├── .env ├── CONVENTIONS.md └── do-lint.sh ├── .github └── workflows │ ├── publish-basic-ida-plugin.yml │ ├── publish-colorize-calls-ida-plugin.yml │ ├── publish-hint-calls-ida-plugin.yml │ ├── publish-idapro-plugin-manager.yml │ ├── publish-multifile-ida-plugin.yml │ ├── publish-navband-visited-ida-plugin.yml │ └── publish-tag-func-ida-plugin.yml ├── .gitignore ├── LICENSE ├── doc └── idapython.rst ├── idawilli ├── __init__.py ├── dbg │ └── __init__.py └── lex_curline.py ├── plugins ├── colorize_calls │ ├── README.md │ ├── colorize_calls.py │ └── pyproject.toml ├── dynamic_hints │ ├── dynamic_hints.py │ └── readme.md ├── hint_calls │ ├── .justfile │ ├── hint_calls.py │ ├── img │ │ └── hint-calls.png │ ├── pyproject.toml │ └── readme.md ├── navband_visited │ ├── README.md │ ├── navband_visited.py │ └── pyproject.toml ├── plugin-manager │ ├── .justfile │ ├── README.md │ ├── doc │ │ └── migrating-a-plugin.md │ ├── examples │ │ ├── basic-ida-plugin │ │ │ ├── README.md │ │ │ ├── hello.py │ │ │ └── pyproject.toml │ │ └── multifile-ida-plugin │ │ │ ├── README.md │ │ │ ├── pyproject.toml │ │ │ └── src │ │ │ └── multifile_ida_plugin │ │ │ ├── __init__.py │ │ │ └── plugin │ │ │ └── __init__.py │ ├── idapro_plugin_manager │ │ ├── __init__.py │ │ └── __main__.py │ ├── img │ │ └── pypi-trusted-publishing-configuration.png │ └── pyproject.toml └── tag_func │ ├── README.md │ ├── pyproject.toml │ └── tag_func.py ├── scripts ├── add_segment │ ├── add_segment.py │ └── readme.md ├── allocate_rwx_page.py ├── color │ └── color.py ├── deob.py ├── find_ptrs │ ├── ida_find_ptrs.py │ └── readme.md ├── fix_ptr_ops │ └── fix_ptr_operands.py ├── go │ ├── go_fixup_fptrs.py │ ├── go_fixup_global_strings.py │ └── go_fixup_inline_strings.py ├── goto_file_offset.py ├── goto_rva.py ├── load_file_rwx.py ├── load_map_file.py ├── parse_uuid.py ├── resolve_ptrs.py ├── save_segment.py ├── windb_symbols │ └── apply_windbg_symbols.py ├── write_file.py └── yara_fn │ ├── readme.md │ └── yara_fn.py ├── setup.py ├── tests ├── readme.md └── test_all.py └── themes ├── colors ├── adwaita-dark │ ├── adwaita-dark.clr │ ├── readme.md │ └── screenshot.png ├── readme.md └── willi │ ├── theme.css │ └── user.css └── skins ├── adwaita-dark ├── icons │ ├── expand.png │ └── spacer.png ├── manifest.xml ├── preview.png ├── readme.md └── stylesheet.qss ├── readme.md └── vscode-dark-wb ├── adwaita-dark.clr ├── icons ├── blank.png ├── close.png ├── drag.png ├── expand.png ├── float.png ├── fullscreen.png └── menu.png ├── manifest.json ├── preview.png ├── stylesheet.less ├── stylesheet.qss └── vscode-dark.clr /.env/CONVENTIONS.md: -------------------------------------------------------------------------------- 1 | - use black formatting 2 | - prefer to provide type hints 3 | - use logging/stderr for status messages and print/stdout for program output 4 | - you can use the following libraries frequently: tqdm, rich, humanize 5 | - avoid adding comments unless the code is confusion or not idiomatic 6 | -------------------------------------------------------------------------------- /.env/do-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status. 4 | set -e 5 | # Treat unset variables as an error when substituting. 6 | set -u 7 | # If any command in a pipeline fails, the pipeline's return status is the value of the last command to exit with a non-zero status. 8 | set -o pipefail 9 | 10 | # Check if exactly one argument (the file path) is provided 11 | if [ "$#" -ne 1 ]; then 12 | echo "Usage: $0 " >&2 13 | exit 1 14 | fi 15 | 16 | FILE_PATH="$1" 17 | 18 | # Check if the provided path is a file 19 | if [ ! -f "$FILE_PATH" ]; then 20 | echo -e "\033[31mError: File '$FILE_PATH' not found.\033[0m" >&2 21 | exit 1 22 | fi 23 | 24 | # Check if the file has a .py extension 25 | if [[ ! "$FILE_PATH" =~ \.py$ ]]; then 26 | echo -e "\033[33mWarning: Skipping linting for non-Python file: '$FILE_PATH'.\033[0m" >&2 27 | exit 0 28 | fi 29 | 30 | eval "$(direnv export bash)" 31 | 32 | echo -e "\033[34mLinting $FILE_PATH...\033[0m" >&2 33 | 34 | echo -e "\033[34mRunning ruff...\033[0m" >&2 35 | uvx ruff check --line-length 120 "$FILE_PATH" 36 | 37 | echo -e "\033[34mRunning ty check...\033[0m" >&2 38 | uvx ty check --ignore unresolved-import "$FILE_PATH" 39 | 40 | echo -e "\033[34mRunning mypy...\033[0m" >&2 41 | uvx mypy --check-untyped-defs --ignore-missing-imports --disable-error-code=import-untyped "$FILE_PATH" 42 | 43 | echo -e "\033[32mLinting completed successfully for $FILE_PATH.\033[0m" >&2 44 | exit 0 45 | -------------------------------------------------------------------------------- /.github/workflows/publish-basic-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish basic-ida-plugin to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/plugin-manager/examples/basic-ida-plugin 27 | pip install setuptools==78.1.1 build==1.2.2 28 | - name: build package 29 | run: | 30 | cd plugins/plugin-manager/examples/basic-ida-plugin 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/plugin-manager/examples/basic-ida-plugin/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/plugin-manager/examples/basic-ida-plugin/dist/* 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/publish-colorize-calls-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish williballenthin-colorize-calls-ida-plugin to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/colorize_calls 27 | pip install setuptools==78.1.1 build==1.2.2 28 | - name: build package 29 | run: | 30 | cd plugins/colorize_calls 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/colorize_calls/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/colorize_calls/dist/* 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-hint-calls-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish williballenthin-hint-calls-ida-plugin to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/hint_calls 27 | pip install setuptools==78.1.1 build==1.2.2 28 | - name: build package 29 | run: | 30 | cd plugins/hint_calls 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/hint_calls/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/hint_calls/dist/* 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-idapro-plugin-manager.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish idapro-plugin-manager to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/plugin-manager 27 | pip install -e .[build] 28 | - name: build package 29 | run: | 30 | cd plugins/plugin-manager 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/plugin-manager/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/plugin-manager/dist/* 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/publish-multifile-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish multifile-ida-plugin to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/plugin-manager/examples/multifile-ida-plugin 27 | pip install setuptools==78.1.1 build==1.2.2 28 | - name: build package 29 | run: | 30 | cd plugins/plugin-manager/examples/multifile-ida-plugin 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/plugin-manager/examples/multifile-ida-plugin/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/plugin-manager/examples/multifile-ida-plugin/dist/* 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/publish-navband-visited-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/publish-navband-visited-ida-plugin.yml 2 | name: publish williballenthin-navband-visited-ida-plugin to pypi 3 | 4 | on: 5 | workflow_dispatch: 6 | # Example trigger: when a new tag starting with 'navband-visited-v' is pushed 7 | # push: 8 | # tags: 9 | # - 'navband-visited-v*' 10 | 11 | permissions: 12 | contents: read 13 | id-token: write # Required for PyPI trusted publishing 14 | 15 | jobs: 16 | pypi-publish: 17 | name: build and publish navband_visited 18 | runs-on: ubuntu-latest 19 | environment: 20 | name: release # If using PyPI trusted publishing environments 21 | permissions: 22 | id-token: write # Required for PyPI trusted publishing 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Set up Python 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: '3.9' # Matches requires-python in pyproject.toml 32 | 33 | - name: Install dependencies (build tools) 34 | run: | 35 | python -m pip install --upgrade pip 36 | pip install setuptools build 37 | 38 | - name: Build package 39 | run: | 40 | cd plugins/navband_visited 41 | python -m build 42 | 43 | - name: Publish package to PyPI 44 | uses: pypa/gh-action-pypi-publish@release/v1 45 | with: 46 | packages-dir: plugins/navband_visited/dist 47 | 48 | - name: Upload package artifacts 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: python-package-navband-visited 52 | path: plugins/navband_visited/dist/* 53 | -------------------------------------------------------------------------------- /.github/workflows/publish-tag-func-ida-plugin.yml: -------------------------------------------------------------------------------- 1 | # use PyPI trusted publishing, as described here: 2 | # https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ 3 | name: publish williballenthin-tag-func-ida-plugin to pypi 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | pypi-publish: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: release 16 | permissions: 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 20 | - name: Set up Python 21 | uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 22 | with: 23 | python-version: '3.13' 24 | - name: Install dependencies 25 | run: | 26 | cd plugins/tag_func 27 | pip install setuptools==78.1.1 build==1.2.2 28 | - name: build package 29 | run: | 30 | cd plugins/tag_func 31 | python -m build 32 | - name: publish package 33 | uses: pypa/gh-action-pypi-publish@v1.12.4 34 | with: 35 | skip-existing: false 36 | packages-dir: plugins/tag_func/dist 37 | # put the artifacts after the publish step, so that they are not uploaded if the publish fails 38 | # since we may have multiple things to publish from the same trigger (release) 39 | - name: upload package artifacts 40 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 41 | with: 42 | path: plugins/tag_func/dist/* 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | .direnv 91 | .venv 92 | .envrc 93 | .env 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /doc/idapython.rst: -------------------------------------------------------------------------------- 1 | segments 2 | -------- 3 | 4 | enumerate segments:: 5 | 6 | Segment = namedtuple('Segment', ['start', 'end', 'name']) 7 | def enum_segments(): 8 | for segstart in idautils.Segments(): 9 | segend = idc.SegEnd(segstart) 10 | segname = idc.SegName(segstart) 11 | yield Segment(segstart, segend, segname) 12 | 13 | heads 14 | ----- 15 | 16 | a `head` is a defined item in an idb. 17 | for example, a defined byte, dword, instruction, etc. 18 | 19 | enumerate heads:: 20 | 21 | for segment in enum_segments(): 22 | for head in idautils.Heads(segment.start, segment.end): 23 | print(hex(head)) 24 | 25 | note that just because there is a byte value at an address does not mean there is a head there. 26 | use ``idc.isHead(idc.getFlags(address))`` to test head-ness. for example, consider the following:: 27 | 28 | 0000 seg000 segment byte public 'CODE' use32 29 | 0000 assume cs:seg000 30 | 0000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing 31 | 0000 db ? ; NOT HEAD 32 | 0001 db ? ; NOT HEAD 33 | 0002 db ? ; NOT HEAD 34 | 0003 db 90h ;  NOT HEAD 35 | 0004 dw 9090h; HEAD 36 | 0006 db 90h ; HEAD 37 | 0007 ; --------------------------------------------------------------------------- 38 | 0007 nop ; HEAD 39 | 0007 ; --------------------------------------------------------------------------- 40 | 41 | although there is a byte at address 0003, it has not been defined like the byte at 0006. 42 | 43 | names and comments can be set an any address, not just heads. 44 | so in the above example, the byte at address 0003 may have a name and comment, but not be considered a head. 45 | however, when fetching comments you need to be a bit careful: heads may span multiple bytes, and fetching comments from any address within the head fetches the head's comments. 46 | given the above example, the following is true:: 47 | 48 | idc.MakeComm(0x4, 'a comment') 49 | assert idc.Comm(0x4) == 'a comment' 50 | assert idc.Comm(0x5) == 'a comment' 51 | 52 | 53 | text encoding 54 | ------------- 55 | 56 | on different systems, IDA may use different codepages. 57 | all IDAPython APIs accept and return the python type ``str`` for strings, and they do not accept the ``unicode`` type. 58 | 59 | for compatibility across users, you should explicitly encode and decode to/from ASCII:: 60 | 61 | idc.MakeName(0x0, u'foobar'.encode('ascii', errors='replace')) 62 | idc.Name(0x0).decode('ascii', errors='replace') 63 | 64 | 65 | commenting 66 | ---------- 67 | 68 | create local comment:: 69 | 70 | if not idc.MakeComm(va, ctext): 71 | logger.warning('failed to create local comment: 0x%x', va) 72 | 73 | delete local comment:: 74 | 75 | if not idc.MakeComm(va, ''): 76 | logger.warning('failed to delete local comment: 0x%x', va) 77 | 78 | create repeatable comment:: 79 | 80 | if not idc.MakeRptCmt(va, ctext): 81 | logger.warning('failed to create repeatable comment: 0x%x', va) 82 | 83 | delete repeatable comment:: 84 | 85 | if not idc.MakeRptCmt(va, ''): 86 | logger.warning('failed to delete repeatable comment: 0x%x', va) 87 | 88 | create anterior comment:: 89 | 90 | for i, line in enumerate(ctext.split('\n')): 91 | if not idc.ExtLinA(va, i, line): 92 | logger.warning('failed to create anterior line comment: 0x%x %d', va, i) 93 | 94 | delete anterior comment:: 95 | 96 | # deleting line 0 deletes all the rest, too 97 | if not idc.DelExtLnA(va, 0): 98 | logger.warning('failed to delete anterior comment: 0x%x', va) 99 | 100 | create posterior comment:: 101 | 102 | for i, line in enumerate(ctext.split('\n')): 103 | if not idc.ExtLinB(va, i, line): 104 | logger.warning('failed to create posterior line comment: 0x%x %d', va, i) 105 | 106 | delete posterior comment:: 107 | 108 | if not idc.DelExtLnB(va, 0): 109 | logger.warning('failed to delete anterior comment: 0x%x', va) 110 | 111 | create function comment:: 112 | 113 | if not idc.SetFunctionCmt(va, ctext, False): 114 | logger.warning('failed to create function local comment: 0x%x', va) 115 | 116 | delete function comment:: 117 | 118 | if not idc.SetFunctionCmt(va, '', False): 119 | logger.warning('failed to delete function local comment: 0x%x', va) 120 | 121 | create repeatable function comment:: 122 | 123 | if not idc.SetFunctionCmt(va, ctext, True): 124 | logger.warning('failed to create function repeatable comment: 0x%x', va) 125 | 126 | delete repeatable function comment:: 127 | 128 | if not idc.SetFunctionCmt(va, '', True): 129 | logger.warning('failed to delete function repeatablecomment: 0x%x', va) 130 | 131 | 132 | types 133 | -------------------------- 134 | 135 | inspect function prototype:: 136 | 137 | tup = idaapi.get_named_type(None, 'CreateServiceA', idaapi.NTF_SYMM) 138 | if tup is not None: 139 | code, type_str, fields_str, cmt, field_cmts, sclass, value = tup 140 | t1 = idaapi.tinfo_t() 141 | t1.deserialize(None, type_str, fields_str, cmt) 142 | print('Number of args: %d' % t1.get_nargs()) 143 | print('Type of arg 0: %s' %t1.get_nth_arg(0)._print()) 144 | print('Size of arg 0: %d' % t1.get_nth_arg(0).get_size()) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /idawilli/__init__.py: -------------------------------------------------------------------------------- 1 | def align(value, alignment=0x1000): 2 | if value % alignment == 0: 3 | return value 4 | return value + (alignment - (value % alignment)) -------------------------------------------------------------------------------- /idawilli/dbg/__init__.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idaapi 3 | import ida_idd 4 | import ida_name 5 | import ida_bytes 6 | import ida_kernwin 7 | import ida_dbg 8 | 9 | # import types for the given macros 10 | idaapi.import_type(idaapi.cvar.idati, 0, "MACRO_NULL") # for NULL 11 | idaapi.import_type(idaapi.cvar.idati, 0, "MACRO_PAGE") # for PAGE_EXECUTE_READWRITE 12 | idaapi.import_type(idaapi.cvar.idati, 0, "MACRO_MEM") # for MEM_COMMIT 13 | 14 | # shortcut to constants 15 | c = ida_idd.Appcall.Consts 16 | 17 | 18 | def allocate_rwx(size): 19 | # this is the explicit way to create an Appcall callable 20 | # see also: `Appcall.proto` 21 | VirtualAlloc = ida_idd.Appcall.typedobj("int __stdcall VirtualAlloc( int lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect);") 22 | VirtualAlloc.ea = ida_name.get_name_ea(0, "kernel32_VirtualAlloc") 23 | ptr = VirtualAlloc(c.NULL, int(size), c.MEM_COMMIT, c.PAGE_EXECUTE_READWRITE) 24 | if ptr == 0: 25 | print("VirtualAlloc failed: 0x%x" % GetLastError()) 26 | raise ValueError("VirtualAlloc failed: 0x%x" % GetLastError()) 27 | ida_dbg.refresh_debugger_memory() 28 | return ptr 29 | 30 | 31 | def GetLastError(): 32 | # this is the concise way to create an Appcall callable. 33 | # symbol name as found in the workspace and function prototype. 34 | return ida_idd.Appcall.proto("kernel32_GetLastError", "DWORD __stdcall GetLastError();")() 35 | 36 | 37 | LoadLibraryA = ida_idd.Appcall.proto("kernel32_LoadLibraryA", "HMODULE __stdcall LoadLibraryA(LPCSTR lpLibFileName);") 38 | GetProcAddress = ida_idd.Appcall.proto("kernel32_GetProcAddress", "FARPROC __stdcall GetProcAddress(HMODULE hModule, LPCSTR lpProcName);") 39 | 40 | 41 | def get_winapi_decl(name): 42 | ''' 43 | fetch the C function declaration for the given Windows API function. 44 | ''' 45 | tup = idaapi.get_named_type(None, name, idaapi.NTF_SYMM) 46 | if tup is None: 47 | raise ValueError("failed to fetch type") 48 | code, type_str, fields_str, cmt, field_cmts, sclass, value = tup 49 | ti = idaapi.tinfo_t() 50 | ti.deserialize(None, type_str, fields_str, cmt) 51 | 52 | # the rendered declaration from IDA doesn't include the function name, 53 | # so insert the function name, naively. 54 | # 55 | # for example; 56 | # 57 | # > DWORD (DWORD a, DWORD b) 58 | # < DWORD foo(DWORD a, DWORD b); 59 | decl = str(ti).replace("(", " " + name + "(") + ";" 60 | 61 | return decl 62 | 63 | 64 | def api(dll, proc): 65 | ''' 66 | get a callable Windows API function. 67 | 68 | Python>idaapi.require("idawilli.dbg") 69 | Python>idawilli.dbg.api("kernel32.dll", "SetLastError")(0x31337) 70 | 0x0L 71 | Python>idawilli.dbg.api("kernel32.dll", "GetLastError")() 72 | 0x31337L 73 | ''' 74 | hmod = LoadLibraryA(dll) 75 | pfunc = GetProcAddress(hmod, proc) 76 | decl = get_winapi_decl(proc) 77 | return ida_idd.Appcall.proto(pfunc.value, decl) 78 | 79 | 80 | class _Module(object): 81 | ''' loaded DLL that supports calling procedures via Appcall ''' 82 | 83 | def __init__(self, dll): 84 | super(_Module, self).__init__() 85 | self.dll = dll if dll.lower().endswith('.dll') else (dll + '.dll') 86 | self.hmod = LoadLibraryA(dll).value 87 | 88 | def __getattr__(self, proc): 89 | if proc == 'dll': 90 | return super(self, _Module).__getattr__(proc) 91 | elif proc == 'hmod': 92 | return super(self, _Module).__getattr__(proc) 93 | 94 | pfunc = GetProcAddress(self.hmod, proc).value 95 | return ida_idd.Appcall.proto(pfunc, get_winapi_decl(proc)) 96 | 97 | 98 | class _API(object): 99 | ''' fake object that creates a _Module on demand ''' 100 | 101 | def __getattr__(self, dll): 102 | return _Module(dll) 103 | 104 | 105 | ''' 106 | Python>idaapi.require("idawilli.dbg") 107 | Python>idawilli.dbg.winapi.kernel32.SetLastError(0x31337) 108 | 0x0L 109 | Python>idawilli.dbg.winapi.kernel32.GetLastError() 110 | 0x31337L 111 | ''' 112 | winapi = _API() 113 | 114 | 115 | def patch_bytes(ea, buf): 116 | for i, b in enumerate(buf): 117 | ida_bytes.patch_byte(ea + i, b) 118 | -------------------------------------------------------------------------------- /idawilli/lex_curline.py: -------------------------------------------------------------------------------- 1 | ''' 2 | split the line returned by `get_custom_viewer_curline` into symbols. 3 | it pulls out the strings, color directives, and escaped characters. 4 | this hex-rays blog post describes how ida uses the special color tags 5 | to describe syntax highlighting: 6 | 7 | http://www.hexblog.com/?p=119 8 | 9 | for example, here's a line that we see in IDA Pro: 10 | 11 | 10056303 008 6A 52 push 52h 12 | 13 | and when we fetch it via `get_custom_viewer_curline`, this is what we get: 14 | 15 | 00000000: 01 13 31 30 30 35 36 33 30 33 02 13 20 01 0C 30 ..10056303.. ..0 16 | 00000010: 30 38 20 02 0C 01 14 36 41 20 35 32 20 02 14 20 08 ....6A 52 .. 17 | 00000020: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 18 | 00000030: 20 01 05 70 75 73 68 02 05 20 20 20 20 01 29 01 ..push.. .). 19 | 00000040: 0C 35 32 68 02 0C 02 29 .52h...) 20 | 21 | note that at offset 0x0 are the bytes | 01 13 |, which is not ascii text. 22 | instead, this indicate "start syntax highlighting using the COLOR_PREFIX theme". 23 | this lexer decodes these bytes into an object you can inspect: 24 | 25 | > for s in lex(curline): 26 | > print(str(s)) 27 | 28 | < COLORON=COLOR_PREFIX 29 | < "10056303" 30 | < COLOROFF=COLOR_PREFIX 31 | < ... 32 | 33 | when building a formatter that processes these symbols, inspect 34 | each object's `.type` property to figure out what it is. then you 35 | can fetch other relevant fields, such as `.color` for `ColorOnSymbol`. 36 | ''' 37 | import idaapi 38 | 39 | 40 | # inverse mapping of color value to name. 41 | # ref: https://www.hex-rays.com/products/ida/support/sdkdoc/group___s_c_o_l_o_r__.html#ga6052470f86411b8b5ffdf4af4bbee225 42 | INV_COLORS = { 43 | 0x1: 'COLOR_DEFAULT', #= 0x01, // Default 44 | 0x2: 'COLOR_REGCMT', #= 0x02, // Regular comment 45 | 0x3: 'COLOR_RPTCMT', #= 0x03, // Repeatable comment (comment defined somewhere else) 46 | 0x4: 'COLOR_AUTOCMT', #= 0x04, // Automatic comment 47 | 0x5: 'COLOR_INSN', #= 0x05, // Instruction 48 | 0x6: 'COLOR_DATNAME', #= 0x06, // Dummy Data Name 49 | 0x7: 'COLOR_DNAME', #= 0x07, // Regular Data Name 50 | 0x8: 'COLOR_DEMNAME', #= 0x08, // Demangled Name 51 | 0x9: 'COLOR_SYMBOL', #= 0x09, // Punctuation 52 | 0xa: 'COLOR_CHAR', #= 0x0A, // Char constant in instruction 53 | 0xb: 'COLOR_STRING', #= 0x0B, // String constant in instruction 54 | 0xc: 'COLOR_NUMBER', #= 0x0C, // Numeric constant in instruction 55 | 0xd: 'COLOR_VOIDOP', #= 0x0D, // Void operand 56 | 0xe: 'COLOR_CREF', #= 0x0E, // Code reference 57 | 0xf: 'COLOR_DREF', #= 0x0F, // Data reference 58 | 0x10: 'COLOR_CREFTAIL', #= 0x10, // Code reference to tail byte 59 | 0x11: 'COLOR_DREFTAIL', #= 0x11, // Data reference to tail byte 60 | 0x12: 'COLOR_ERROR', #= 0x12, // Error or problem 61 | 0x13: 'COLOR_PREFIX', #= 0x13, // Line prefix 62 | 0x14: 'COLOR_BINPREF', #= 0x14, // Binary line prefix bytes 63 | 0x15: 'COLOR_EXTRA', #= 0x15, // Extra line 64 | 0x16: 'COLOR_ALTOP', #= 0x16, // Alternative operand 65 | 0x17: 'COLOR_HIDNAME', #= 0x17, // Hidden name 66 | 0x18: 'COLOR_LIBNAME', #= 0x18, // Library function name 67 | 0x19: 'COLOR_LOCNAME', #= 0x19, // Local variable name 68 | 0x1A: 'COLOR_CODNAME', #= 0x1A, // Dummy code name 69 | 0x1B: 'COLOR_ASMDIR', #= 0x1B, // Assembler directive 70 | 0x1C: 'COLOR_MACRO', #= 0x1C, // Macro 71 | 0x1D: 'COLOR_DSTR', #= 0x1D, // String constant in data directive 72 | 0x1E: 'COLOR_DCHAR', #= 0x1E, // Char constant in data directive 73 | 0x1F: 'COLOR_DNUM', #= 0x1F, // Numeric constant in data directive 74 | 0x20: 'COLOR_KEYWORD', #= 0x20, // Keywords 75 | 0x21: 'COLOR_REG', #= 0x21, // Register name 76 | 0x22: 'COLOR_IMPNAME', #= 0x22, // Imported name 77 | 0x23: 'COLOR_SEGNAME', #= 0x23, // Segment name 78 | 0x24: 'COLOR_UNKNAME', #= 0x24, // Dummy unknown name 79 | 0x25: 'COLOR_CNAME', #= 0x25, // Regular code name 80 | 0x26: 'COLOR_UNAME', #= 0x26, // Regular unknown name 81 | 0x27: 'COLOR_COLLAPSED',#= 0x27, // Collapsed line 82 | 83 | # // Fictive colors 84 | 0x28: 'COLOR_ADDR', #= 0x28, // hidden address marks 85 | # // The address is represented as 8digit 86 | # // hex number: 01234567 87 | # // It doesn't have COLOR_OFF pair 88 | # // NB: for 64-bit IDA, the address is 16digit 89 | 90 | 0x29: 'COLOR_OPND1', #= COLOR_ADDR+1, // Instruction operand 1 91 | 0x2A: 'COLOR_OPND2', #= COLOR_ADDR+2, // Instruction operand 2 92 | 0x2B: 'COLOR_OPND3', #= COLOR_ADDR+3, // Instruction operand 3 93 | 0x2C: 'COLOR_OPND4', #= COLOR_ADDR+4, // Instruction operand 4 94 | 0x2D: 'COLOR_OPND5', #= COLOR_ADDR+5, // Instruction operand 5 95 | 0x2E: 'COLOR_OPND6', #= COLOR_ADDR+6, // Instruction operand 6 96 | 97 | 0x32: 'COLOR_UTF8', #= COLOR_ADDR+10;// Following text is UTF-8 encoded 98 | } 99 | 100 | 101 | def get_color_name(color): 102 | return INV_COLORS[color] 103 | 104 | 105 | class Symbol(object): 106 | def __init__(self, type): 107 | super(Symbol, self).__init__() 108 | self.type = type 109 | 110 | def __str__(self): 111 | raise NotImplementedError() 112 | 113 | 114 | class StringSymbol(Symbol): 115 | def __init__(self, string): 116 | super(StringSymbol, self).__init__('string') 117 | self.string = string 118 | 119 | def __str__(self): 120 | return 'STRING=' + self.string 121 | 122 | 123 | class ColorOnSymbol(Symbol): 124 | def __init__(self, color): 125 | super(ColorOnSymbol, self).__init__('coloron') 126 | self.color = ord(color) 127 | 128 | def __str__(self): 129 | return 'COLORON=' + get_color_name(self.color) 130 | 131 | 132 | class ColorOffSymbol(Symbol): 133 | def __init__(self, color): 134 | super(ColorOffSymbol, self).__init__('coloroff') 135 | self.color = ord(color) 136 | 137 | def __str__(self): 138 | return 'COLOROFF=' + get_color_name(self.color) 139 | 140 | 141 | class ColorInvSymbol(Symbol): 142 | def __init__(self): 143 | super(ColorInvSymbol, self).__init__('colorinv') 144 | 145 | def __str__(self): 146 | return 'COLORINV' 147 | 148 | 149 | def lex(curline): 150 | ''' 151 | split the line returned by `get_custom_viewer_curline` into symbols. 152 | it pulls out the strings, color directives, and escaped characters. 153 | 154 | Args: 155 | curline (str): a line returned by `idaapi.get_custom_viewer_curline` 156 | 157 | Returns: 158 | generator: generator of Symbol subclass instances 159 | ''' 160 | 161 | offset = 0 162 | cur_word = [] 163 | while offset < len(curline): 164 | 165 | c = curline[offset] 166 | 167 | if c == idaapi.COLOR_ON: 168 | if cur_word: 169 | yield StringSymbol(''.join(cur_word)) 170 | cur_word = [] 171 | 172 | offset += 1 173 | color = curline[offset] 174 | 175 | yield ColorOnSymbol(color) 176 | offset += 1 177 | 178 | elif c == idaapi.COLOR_OFF: 179 | if cur_word: 180 | yield StringSymbol(''.join(cur_word)) 181 | cur_word = [] 182 | 183 | offset += 1 184 | color = curline[offset] 185 | 186 | yield ColorOffSymbol(color) 187 | offset += 1 188 | 189 | elif c == idaapi.COLOR_ESC: 190 | if cur_word: 191 | yield StringSymbol(''.join(cur_word)) 192 | cur_word = [] 193 | 194 | offset += 1 195 | c = curline[offset] 196 | 197 | cur_word.append(c) 198 | offset += 1 199 | 200 | elif c == idaapi.COLOR_INV: 201 | if cur_word: 202 | yield StringSymbol(''.join(cur_word)) 203 | cur_word = [] 204 | 205 | yield ColorInvSymbol() 206 | offset += 1 207 | 208 | else: 209 | cur_word.append(c) 210 | offset += 1 211 | 212 | -------------------------------------------------------------------------------- /plugins/colorize_calls/README.md: -------------------------------------------------------------------------------- 1 | # Colorize Calls IDA Pro Plugin 2 | 3 | IDA Pro plugin to colorize call instructions and add a prefix in the disassembly listing. 4 | This doesn't touch the database, it dynamically updates the view as you browse, so you don't have to worry about bothering your colleagues if you share the .idb. 5 | 6 | ## Features 7 | 8 | - **Background Color:** Changes the background color of `call` instructions in the disassembly view. 9 | - **Instruction Prefix:** Adds a `>>>` prefix to `call` instructions. 10 | 11 | ## Installation 12 | 13 | Assuming you have the [IDA Pro Plugin Manager](https://github.com/williballenthin/idawilli/tree/master/plugins/plugin-manager/), install via pip: 14 | 15 | ```bash 16 | pip install williballenthin-colorize-calls-ida-plugin 17 | ``` 18 | 19 | Make sure to use the pip from your IDAPython installation. 20 | -------------------------------------------------------------------------------- /plugins/colorize_calls/colorize_calls.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | import idaapi 4 | import ida_ua 5 | import ida_lines 6 | import ida_idaapi 7 | 8 | 9 | class ColorHooks(idaapi.IDP_Hooks): 10 | def ev_get_bg_color(self, color, ea): 11 | """ 12 | Get item background color. 13 | Plugins can hook this callback to color disassembly lines dynamically 14 | 15 | ```c 16 | // background color in RGB 17 | typedef uint32 bgcolor_t; 18 | ``` 19 | ref: https://hex-rays.com/products/ida/support/sdkdoc/pro_8h.html#a3df5040891132e50157aee66affdf1de 20 | 21 | args: 22 | color: (bgcolor_t *), out 23 | ea: (::ea_t) 24 | 25 | returns: 26 | retval 0: not implemented 27 | retval 1: color set 28 | """ 29 | mnem = ida_ua.print_insn_mnem(ea) 30 | 31 | if mnem == "call": 32 | bgcolor = ctypes.cast(int(color), ctypes.POINTER(ctypes.c_int)) 33 | # TODO: make this configurable 34 | bgcolor[0] = 0xDDDDDD 35 | return 1 36 | 37 | else: 38 | return 0 39 | 40 | def ev_out_mnem(self, ctx) -> int: 41 | """ 42 | Generate instruction mnemonics. 43 | This callback should append the colored mnemonics to ctx.outbuf 44 | Optional notification, if absent, out_mnem will be called. 45 | 46 | args: 47 | ctx: (outctx_t *) 48 | 49 | returns: 50 | retval 1: if appended the mnemonics 51 | retval 0: not implemented 52 | """ 53 | mnem = ctx.insn.get_canon_mnem() 54 | if mnem == "call": 55 | # you can manipulate this, but note that it affects `ida_ua.print_insn_mnem` which is inconvenient for formatting. 56 | # also, you only have access to theme colors, like COLOR_PREFIX, not arbitrary control. 57 | ctx.out_custom_mnem("call") 58 | return 1 59 | 60 | else: 61 | return 0 62 | 63 | 64 | # the only way to install this is by instantiating an instance *from within a plugin*. 65 | class CallPrefix(ida_lines.user_defined_prefix_t): 66 | def __init__(self): 67 | super().__init__(len(">>>")) 68 | 69 | def get_user_defined_prefix(self, ea, insn, lnnum, indent, line): 70 | mnem = insn.get_canon_mnem() 71 | 72 | if mnem == "call": 73 | return ">>>" 74 | 75 | else: 76 | return " " 77 | 78 | 79 | class ColorizeCallsPlugin(ida_idaapi.plugin_t): 80 | flags = ida_idaapi.PLUGIN_UNL | ida_idaapi.PLUGIN_MULTI 81 | comment = "Colorize call instructions and add a prefix" 82 | help = "Colorize call instructions and add a prefix" 83 | wanted_name = "Colorize Calls" 84 | wanted_hotkey = "" 85 | 86 | def __init__(self): 87 | self.prefix = None 88 | self.hooks = ColorHooks() 89 | 90 | def init(self): 91 | self.prefix = CallPrefix() 92 | self.hooks.hook() 93 | return ida_idaapi.PLUGIN_KEEP 94 | 95 | def term(self): 96 | self.prefix = None 97 | self.hooks.unhook() 98 | 99 | 100 | def PLUGIN_ENTRY(): 101 | return ColorizeCallsPlugin() 102 | -------------------------------------------------------------------------------- /plugins/colorize_calls/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "williballenthin-colorize-calls-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "IDA Pro plugin to colorize call instructions and add a prefix" 7 | version = "0.1.0" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "colorize_calls" 15 | 16 | [tool.setuptools] 17 | py-modules = ["colorize_calls"] 18 | -------------------------------------------------------------------------------- /plugins/dynamic_hints/dynamic_hints.py: -------------------------------------------------------------------------------- 1 | ''' 2 | example of: 3 | - providing custom UI hints with dynamic data from Python 4 | 5 | in this silly example, we display UI hints with the current timestamp. 6 | a more useful plugin might inspect the hovered line, and display some documentation. 7 | 8 | Author: Willi Ballenthin 9 | Licence: Apache 2.0 10 | ''' 11 | import sys 12 | import datetime 13 | 14 | import idc 15 | import idaapi 16 | import idautils 17 | 18 | 19 | class HintsHooks(idaapi.UI_Hooks): 20 | def get_custom_viewer_hint(self, view, place): 21 | curline = idaapi.get_custom_viewer_curline(view, True) 22 | _, x, y = idaapi.get_custom_viewer_place(view, True) 23 | ea = place.toea() 24 | 25 | return ('0x%08X: %s' % (place.toea(), datetime.datetime.now().isoformat(' ')), 1) 26 | 27 | def get_ea_hint(self, ea): 28 | return datetime.datetime.now().isoformat(' ') 29 | 30 | 31 | class DynHints2Plugin(idaapi.plugin_t): 32 | flags = idaapi.PLUGIN_KEEP 33 | comment = "Display dynamically-generated hints (2)." 34 | 35 | help = "Display dynamically-generated hints (2)." 36 | wanted_name = "DynHints2" 37 | wanted_hotkey = "Ctrl-[" 38 | 39 | 40 | def init(self): 41 | self.hooks = HintsHooks() 42 | return idaapi.PLUGIN_OK 43 | 44 | def run(self, arg): 45 | print('hints2: run') 46 | self.hooks.hook() 47 | 48 | def term(self): 49 | print('hints2: term') 50 | self.hooks.unhook() 51 | 52 | 53 | def PLUGIN_ENTRY(): 54 | return DynHints2Plugin() 55 | -------------------------------------------------------------------------------- /plugins/dynamic_hints/readme.md: -------------------------------------------------------------------------------- 1 | # dynamic_hints 2 | 3 | an example plugin that demonstrates how to provide custom hints with dynamic data. 4 | 5 | # installation 6 | 7 | copy the python file to `%IDADIR%/plugins/`. 8 | -------------------------------------------------------------------------------- /plugins/hint_calls/.justfile: -------------------------------------------------------------------------------- 1 | isort: 2 | uvx isort --length-sort --profile black --line-length 120 hint_calls.py 3 | 4 | black: 5 | uvx black --line-length 120 hint_calls.py 6 | 7 | ruff: 8 | uvx ruff check --line-length 120 hint_calls.py 9 | 10 | ty: 11 | uvx ty check --ignore unresolved-import hint_calls.py 12 | 13 | mypy: 14 | uvx mypy --check-untyped-defs --ignore-missing-imports hint_calls.py 15 | 16 | lint: 17 | -just isort 18 | -just black 19 | -just ruff 20 | -just ty 21 | -just mypy 22 | 23 | clean: 24 | -rm -rf dist/ hint_calls_ida_plugin.egg-info/ 25 | 26 | build: 27 | python -m build --wheel 28 | -------------------------------------------------------------------------------- /plugins/hint_calls/img/hint-calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/plugins/hint_calls/img/hint-calls.png -------------------------------------------------------------------------------- /plugins/hint_calls/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "williballenthin-hint-calls-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "IDA Pro plugin to display popup function hints for the referenced calls and strings" 7 | version = "0.1.2" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "hint_calls" 15 | 16 | [build-system] 17 | requires = ["setuptools>=61.0"] 18 | build-backend = "setuptools.build_meta" 19 | 20 | [tool.setuptools] 21 | # This places `hint_calls.py` directly into `site-packages` (gross), 22 | # and lets you do `import hint_calls` (nice). 23 | # So, I don't love this, but it works. 24 | # 25 | # Too bad this doesn't get picked up from the entry points above. 26 | py-modules = ["hint_calls"] 27 | -------------------------------------------------------------------------------- /plugins/hint_calls/readme.md: -------------------------------------------------------------------------------- 1 | # Hint Calls IDA Pro Plugin 2 | 3 | IDA Pro plugin to display popup function hints for the referenced calls and strings. 4 | 5 | ![Screeshot of the plugin in action](./img/hint-calls.png) 6 | 7 | ## Installation 8 | 9 | Assuming you have the [IDA Pro Plugin Manager](https://github.com/williballenthin/idawilli/tree/master/plugins/plugin-manager/), install via pip: 10 | 11 | ```bash 12 | pip install williballenthin-hint-calls-ida-plugin 13 | ``` 14 | 15 | Make sure to use the pip from your IDAPython installation. 16 | -------------------------------------------------------------------------------- /plugins/navband_visited/README.md: -------------------------------------------------------------------------------- 1 | # Navband Visited IDA Pro Plugin 2 | 3 | IDA Pro plugin that tracks and records all disassembly addresses you visit during your analysis, highlighting these visited addresses in IDA's navigation band. 4 | 5 | ## Features 6 | 7 | - Automatically records addresses as you navigate through them in the disassembly view. 8 | - Colors the corresponding locations in the navigation band (navband) black to indicate they have been visited. 9 | - Persists as long as IDA is open, helping you keep track of explored code regions. 10 | 11 | ## Installation 12 | 13 | Assuming you have the [IDA Pro Plugin Manager](https://github.com/williballenthin/idawilli/tree/master/plugins/plugin-manager) (or a compatible setup that recognizes `idapro.plugins` entry points), install via pip: 14 | 15 | ```bash 16 | pip install williballenthin-navband-visited-ida-plugin 17 | ``` 18 | 19 | Make sure to use the `pip` associated with your IDAPython environment. 20 | 21 | ## Publishing 22 | 23 | This plugin is available on PyPI: 24 | [https://pypi.org/project/williballenthin-navband-visited-ida-plugin/](https://pypi.org/project/williballenthin-navband-visited-ida-plugin/) 25 | 26 | The GitHub Actions workflow for publishing is defined in [`.github/workflows/publish-navband-visited-ida-plugin.yml`](../../.github/workflows/publish-navband-visited-ida-plugin.yml). 27 | -------------------------------------------------------------------------------- /plugins/navband_visited/navband_visited.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import idaapi 4 | import ida_bytes 5 | import ida_kernwin 6 | 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class NavbandHooks(ida_kernwin.UI_Hooks): 12 | def __init__(self): 13 | super().__init__() 14 | self.addresses = set() 15 | self.orig_colorizer = None 16 | 17 | def screen_ea_changed(self, ea, _prev_ea): 18 | if ea == idaapi.BADADDR: 19 | return 20 | 21 | item_size = ida_bytes.get_item_size(ea) 22 | if item_size == 0: 23 | # if not an item head or unexplored, mark at least one byte 24 | item_size = 1 25 | 26 | for i in range(item_size): 27 | self.addresses.add(ea + i) 28 | 29 | def colorizer(self, ea, nbytes): 30 | if ea in self.addresses: 31 | return 0x000000 # black 32 | 33 | if self.orig_colorizer: 34 | return ida_kernwin.call_nav_colorizer(self.orig_colorizer, ea, nbytes) 35 | 36 | return None 37 | 38 | def activate(self): 39 | self.hook() 40 | self.orig_colorizer = ida_kernwin.set_nav_colorizer(self.colorizer) 41 | logger.debug("hooks activated") 42 | 43 | def deactivate(self): 44 | if self.orig_colorizer is not None: 45 | ida_kernwin.set_nav_colorizer(self.orig_colorizer) 46 | self.orig_colorizer = None 47 | 48 | self.unhook() 49 | logger.debug("hooks deactivated") 50 | 51 | 52 | class NavbandVisitedPlugin(idaapi.plugin_t): 53 | flags = idaapi.PLUGIN_KEEP | idaapi.PLUGIN_MULTI 54 | comment = "Tracks and records all visited addresses, showing in the navband" 55 | help = "This plugin records all the addresses you visit during your analysis, showing the visited addresses in the navband" 56 | wanted_name = "Navband Visited Indicator" 57 | wanted_hotkey = "" 58 | 59 | def __init__(self): 60 | super().__init__() 61 | self.hooks_manager = None 62 | 63 | def init(self): 64 | self.hooks_manager = NavbandHooks() 65 | self.hooks_manager.activate() 66 | return idaapi.PLUGIN_KEEP 67 | 68 | def term(self): 69 | if self.hooks_manager: 70 | self.hooks_manager.deactivate() 71 | self.hooks_manager = None 72 | 73 | 74 | def PLUGIN_ENTRY(): 75 | return NavbandVisitedPlugin() 76 | -------------------------------------------------------------------------------- /plugins/navband_visited/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "williballenthin-navband-visited-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "IDA Pro plugin to highlight visited addresses in the navigation band." 7 | version = "0.1.0" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "navband_visited" 15 | 16 | [build-system] 17 | requires = ["setuptools>=61.0"] 18 | build-backend = "setuptools.build_meta" 19 | 20 | [tool.setuptools] 21 | py-modules = ["navband_visited"] 22 | -------------------------------------------------------------------------------- /plugins/plugin-manager/.justfile: -------------------------------------------------------------------------------- 1 | isort: 2 | uvx isort --length-sort --profile black --line-length 120 idapro_plugin_manager 3 | 4 | black: 5 | uvx black --line-length 120 idapro_plugin_manager 6 | 7 | ruff: 8 | uvx ruff check --line-length 120 idapro_plugin_manager 9 | 10 | ty: 11 | uvx ty check --ignore unresolved-import 12 | 13 | mypy: 14 | uvx mypy --check-untyped-defs --ignore-missing-imports --disable-error-code=import-untyped idapro_plugin_manager 15 | 16 | lint: 17 | -just isort 18 | -just black 19 | -just ruff 20 | -just ty 21 | -just mypy 22 | -------------------------------------------------------------------------------- /plugins/plugin-manager/README.md: -------------------------------------------------------------------------------- 1 | # IDA Pro Plugin Manager 2 | 3 | The IDA Pro Plugin Manager is a tool to help you discover, install, and manage IDA Pro plugins distributed via a central index. It should be *very easy* for you extend the capabilities of IDA Pro with plugins, whether they are written in IDAPython or compiled languages like C/C++. 4 | 5 | Quickstart: 6 | ```bash 7 | # one time installation 8 | $ pip install idapro-plugin-manager 9 | $ ippm register 10 | 11 | # find some plugins 12 | $ ippm list 13 | $ ippm show williballenthin-hint-calls-ida-plugin 14 | $ ippm install williballenthin-hint-calls-ida-plugin 15 | $ ippm update williballenthin-hint-calls-ida-plugin 16 | $ ippm update-all 17 | $ ippm remove williballenthin-hint-calls-ida-plugin 18 | ``` 19 | 20 | Read on for details: 21 | - [Installation Instructions](#installation) 22 | - [Command-Line Tool (`ippm`)](#command-line-tool-ippm) 23 | - [Packaging Plugins](#packaging-plugins) 24 | - [Entry Points](#entry-points) 25 | 26 | ## Installation 27 | 28 | There are two steps: 29 | 30 | 1. to fetch the plugin manager 31 | 2. to register the plugin manager with IDA Pro 32 | 33 | Then you can install plugins via `pip` directly. 34 | 35 | ### 1. Fetch the package from PyPI 36 | 37 | The plugin manager is distributed via PyPI, so install it via `pip`: 38 | 39 | ```bash 40 | $ pip install idapro-plugin-manager 41 | ``` 42 | 43 | Make sure to use the `pip` from your IDAPython installation, which [I recommend to be a virtual environment](https://williballenthin.com/post/using-a-virtualenv-for-idapython/). 44 | 45 | You can find the location of the `pip` executable by running the following within your IDAPython console in IDA Pro: 46 | 47 | ```python 48 | Python>import subprocess 49 | Python>subprocess.run(["which", "pip"], capture_output=True).stdout.decode("utf-8").strip() 50 | '/Users/user/.idapro/venv/bin/pip' 51 | ``` 52 | 53 | (TODO: check this works on Windows, too.) 54 | 55 | ### 2. Register the plugin manager in IDA Pro 56 | 57 | Run the following command to automatically register the plugin manager with IDA Pro: 58 | 59 | ```bash 60 | $ ippm register 61 | ``` 62 | 63 | This installs the bootstrap plugin to your IDA Pro plugins directory. You only have to do this once, even if you upgrade IDA. 64 | 65 | ## Command-Line Tool (`ippm`) 66 | 67 | The IDA Pro Plugin Manager also comes with a command-line tool, `ippm`, to help you discover and manage plugins. 68 | 69 | ### Listing Available Plugins 70 | 71 | To see a list of IDA Pro plugins available on PyPI, use the `list` command: 72 | 73 | ```bash 74 | $ ippm list 75 | Available IDA Pro Plugins on PyPI 76 | ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 77 | ┃ Name ┃ Last Release ┃ Summary ┃ 78 | ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ 79 | │ basic-ida-plugin (installed) │ 0.1.0 (Jun 02, 2025) │ Example IDA Plugin │ 80 | │ multifile-ida-plugin │ 0.1.0 (Jun 02, 2025) │ Example IDA Plugin with multiple files │ 81 | │ williballenthin-colorize-calls-ida-plugin │ 0.1.0 (Jun 03, 2025) │ IDA Pro plugin to colorize call │ 82 | │ │ │ instructions and add a prefix │ 83 | │ williballenthin-hint-calls-ida-plugin │ 0.1.2 (Jun 03, 2025) │ IDA Pro plugin to display popup │ 84 | │ │ │ function hints for the referenced │ 85 | │ │ │ calls and strings │ 86 | │ williballenthin-navband-visited-ida-plugin │ 0.1.0 (Jun 03, 2025) │ IDA Pro plugin to highlight visited │ 87 | │ │ │ addresses in the navigation band. │ 88 | │ williballenthin-tag-func-ida-plugin │ 0.1.0 (Jun 03, 2025) │ IDA Pro plugin for tagging functions │ 89 | │ │ │ into folders │ 90 | └────────────────────────────────────────────┴──────────────────────┴────────────────────────────────────────┘ 91 | ``` 92 | 93 | This command queries PyPI for packages that appear to be IDA Pro plugins (based on naming conventions like `idapro-plugin-*`, `*-ida-plugin`, etc.). 94 | 95 | 96 | ### Showing Plugin Details 97 | 98 | To view detailed information about a specific plugin, use the `show` command followed by the plugin's name as it appears on PyPI: 99 | 100 | ```bash 101 | $ ippm show williballenthin-tag-func-ida-plugin 102 | ┌─────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┐ 103 | │ Name │ williballenthin-tag-func-ida-plugin │ 104 | │ Version │ 0.1.0 │ 105 | │ Summary │ IDA Pro plugin for tagging functions into folders │ 106 | │ Author │ Willi Ballenthin │ 107 | │ License │ Apache-2.0 │ 108 | │ Requires Python │ >=3.9 │ 109 | │ Package URL │ https://pypi.org/project/williballenthin-tag-func-ida-plugin/ │ 110 | │ Project URL │ https://pypi.org/project/williballenthin-tag-func-ida-plugin/ │ 111 | │ Release URL │ https://pypi.org/project/williballenthin-tag-func-ida-plugin/0.1.0/ │ 112 | │ Version History │ 0.1.0 Jun 03, 2025 │ 113 | │ Description (text/markdown) │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ 114 | │ │ ┃ Tag Function IDA Pro Plugin ┃ │ 115 | │ │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ 116 | │ │ │ 117 | │ │ IDA Pro plugin for tagging functions into folders. │ 118 | │ │ │ 119 | │ │ │ 120 | │ │ Installation │ 121 | │ │ │ 122 | │ │ Assuming you have the IDA Pro Plugin Manager, install via pip: │ 123 | │ │ │ 124 | │ │ │ 125 | │ │ pip install williballenthin-tag-func-ida-plugin │ 126 | │ │ │ 127 | │ │ │ 128 | │ │ Make sure to use the pip from your IDAPython installation. │ 129 | └─────────────────────────────┴────────────────────────────────────────────────────────────────────────────── 130 | ``` 131 | 132 | ### Installing Plugins 133 | 134 | To install a plugin: 135 | 136 | ```bash 137 | $ ippm install multifile-ida-plugin 138 | Installing plugin: multifile-ida-plugin 139 | Successfully installed multifile-ida-plugin 140 | Collecting multifile-ida-plugin 141 | Downloading multifile_ida_plugin-0.1.0-py3-none-any.whl.metadata (1.8 kB) 142 | Downloading multifile_ida_plugin-0.1.0-py3-none-any.whl (2.8 kB) 143 | Installing collected packages: multifile-ida-plugin 144 | Successfully installed multifile-ida-plugin-0.1.0 145 | ``` 146 | 147 | 148 | ### Updating Plugins 149 | To update a plugin to the latest version: 150 | 151 | ```bash 152 | $ ippm update multifile-ida-plugin 153 | Updating plugin: multifile-ida-plugin 154 | multifile-ida-plugin is already up to date 155 | Requirement already satisfied: multifile-ida-plugin in ./.venv/lib/python3.12/site-packages (0.1.0) 156 | ``` 157 | 158 | ### Updating All Plugins 159 | 160 | To update all installed plugins to their latest versions: 161 | 162 | ```bash 163 | $ ippm update-all 164 | Finding installed IDA Pro plugins... 165 | Found 2 installed IDA Pro plugin(s): 166 | - basic-ida-plugin 167 | - multifile-ida-plugin 168 | 169 | Checking for updates... 170 | basic-ida-plugin: 0.1.0 (up to date) 171 | multifile-ida-plugin: 0.1.0 (up to date) 172 | 173 | All plugins are up to date! 174 | ``` 175 | 176 | ### Removing Plugins 177 | 178 | To remove an installed plugin: 179 | 180 | ```bash 181 | $ ippm remove multifile-ida-plugin 182 | Removing plugin: multifile-ida-plugin 183 | Successfully removed multifile-ida-plugin 184 | ``` 185 | 186 | ## Packaging Plugins 187 | 188 | Plugins are distributed via PyPI, which is usually (but not always) used for Python packages. 189 | We use Python-style metadata, such as a `pyproject.toml` file, to describe the plugin. 190 | 191 | By adding an "entry point" for the `idapro.plugins` group, 192 | we can register a plugin with the IDA Pro Plugin Manager. 193 | 194 | Here are some example plugins: 195 | - [basic-ida-plugin](/plugins/plugin-manager/examples/basic-ida-plugin/) 196 | - [multifile-ida-plugin](/plugins/plugin-manager/examples/multifile-ida-plugin/) 197 | 198 | Let's walk through `basic-ida-plugin`, which is a simple IDA plugin with a single file: `hello.py`. 199 | (Recall that IDAPython plugins should have a function named `PLUGIN_ENTRY` that's used to initialize the plugin.) 200 | 201 | The package structure looks like: 202 | 203 | basic-ida-plugin/ 204 | ├── hello.py 205 | └── pyproject.toml 206 | 207 | with the `pyproject.toml` contents: 208 | 209 | ```toml 210 | [project] 211 | name = "basic-ida-plugin" 212 | ... 213 | 214 | [project.entry-points.'idapro.plugins'] 215 | idapython = 'hello' 216 | ``` 217 | 218 | and `hello.py` contents: 219 | 220 | ```py 221 | import idaapi 222 | 223 | class hello_plugmod_t(idaapi.plugmod_t): 224 | def run(self, arg): 225 | print("Hello world! (py)") 226 | return 0 227 | 228 | class hello_plugin_t(idaapi.plugin_t): 229 | flags = idaapi.PLUGIN_UNL | idaapi.PLUGIN_MULTI 230 | comment = "This is a comment" 231 | help = "This is help" 232 | wanted_name = "Hello Python plugin" 233 | wanted_hotkey = "Alt-F8" 234 | 235 | def init(self): 236 | print("hello from init") 237 | return hello_plugmod_t() 238 | 239 | def PLUGIN_ENTRY(): 240 | return hello_plugin_t() 241 | ``` 242 | 243 | The `pyproject.toml` entry point references `hello` Python module that contains the plugin code. 244 | Our plugin manager knows how to inspect all installed Python packages 245 | and find plugins via the metadata, including `basic-ida-plugin`. 246 | 247 | I packaged this plugin and uploaded it to PyPI, so you can install it like this: 248 | 249 | pip install basic-ida-plugin 250 | 251 | If you have a local plugin in development, you can use other Python idioms, like: 252 | 253 | pip install --editable /path/to/basic-ida-plugin/source 254 | 255 | There is a more comprehensive miration guide found in [doc/migrating-a-plugin.md](doc/migrating-a-plugin.md). 256 | 257 | 258 | # Entry Points 259 | 260 | Each package contains a single plugin. 261 | The keys within the entry points section describe the sort of plugin thats available: 262 | - `idapython` for IDAPython-based plugins 263 | - target triple for compiled plugins, like `aarch64-apple-darwin` 264 | 265 | ## Examples 266 | 267 | For a single Python file named `hello.py` (like above): 268 | 269 | ```toml 270 | [project.entry-points.'idapro.plugins'] 271 | idapython = 'hello' # note: there's no file extension 272 | ``` 273 | 274 | For a plugin within a larger Python package, such as for the default plugin 275 | provided by capa in [capa.ida.plugin](https://github.com/mandiant/capa/blob/master/capa/ida/plugin/__init__.py): 276 | 277 | ```toml 278 | [project.entry-points.'idapro.plugins'] 279 | idapython = 'capa.ida.plugin' 280 | ``` 281 | 282 | In this scenario, the entry point section would be in capa's `pyproject.toml`, 283 | so you'd install `capa` within your IDAPython virtualenv and the plugin would 284 | now be available within IDA. 285 | 286 | Since the name `capa` doesn't match the `idapro-plugin-*` prefix for IDA plugins 287 | available on PyPI, it would have to be registered with the extras list. 288 | 289 | ### Native plugins 290 | 291 | For a compiled plugin, create a Python package with the compiled artifacts stored within the "Python" package: 292 | 293 | ```sh 294 | ❯ eza --tree --level=2 --long --git 295 | native-ida-plugin 296 | ├── bin 297 | │ └── native_ida_plugin 298 | │ ├── __init__.py 299 | │ ├── mysample.so 300 | │ ├── mysample.dll 301 | │ ├── mysample_aarch64.dylib 302 | │ └── mysample_x86_64.dylib 303 | ├── pyproject.toml 304 | └── README.md 305 | ``` 306 | 307 | And use target triple names as the entry point keys to specify filenames for the compiled artifacts: 308 | 309 | ```toml 310 | [project.entry-points.'idapro.plugins'] 311 | aarch64-apple-darwin = "native_ida_plugin:mysample_aarch64" # note: extensions are automatically appended 312 | x86_64-apple-darwin = "native_ida_plugin:mysample_x86_64" 313 | x86_64-unknown-linux = "native_ida_plugin:mysample" 314 | x86_64-pc-windows = "native_ida_plugin:mysample" 315 | 316 | [build-system] 317 | requires = ["setuptools>=61.0"] 318 | build-backend = "setuptools.build_meta" 319 | 320 | [tool.setuptools] 321 | package-dir = {"" = "bin"} # Python package data is found in "bin/" directory. 322 | # "src/" is the default, but we'll use "bin/" for all this binary data. 323 | 324 | [tool.setuptools.package-data] 325 | "native_ida_plugin" = [ 326 | # filenames relative to: bin/native_ida_plugin/ 327 | "mysample.*", 328 | "mysample_aarch64.*", 329 | "mysample_x86_64.*", 330 | ] 331 | ``` 332 | 333 | Unfortunately the entry point value (`native_ida_plugin:mysample_aarch64`) cannot contain `/` or `.`, 334 | so the compiled artifacts are best placed in that package root directory. 335 | Its also possible to use a layout like `native_ida_plugin.aarch64:mysample`. 336 | 337 | Technically, the entry point value is supposed to point to a Python object. 338 | We abuse this a bit, because we assign a specific meaning to entry point keys like `aarch64-apple-darwin`. 339 | To make this safe, add package level variables to `bin/native_ida_plugin/__init__.py`: 340 | 341 | ```py 342 | mysample_aarch64 = None 343 | mysample_x86_64 = None 344 | mysample = None 345 | ``` 346 | 347 | But this is not strictly necessary today. 348 | 349 | # Native Dependencies 350 | 351 | (this is experimental/not implemented yet/just an idea) 352 | 353 | Extend the search path for libraries like this: 354 | 355 | ```toml 356 | [tool.idapro.plugins.lib] 357 | 'aarch64-apple-darwin' = 'lib/darwin-aarch64/' 358 | 'x86_64-apple-darwin' = 'lib/darwin-x86_64/' 359 | ``` 360 | 361 | Which is useful if your plugin depends on additional native libraries; for example, OpenSSL. 362 | -------------------------------------------------------------------------------- /plugins/plugin-manager/doc/migrating-a-plugin.md: -------------------------------------------------------------------------------- 1 | # Migrating an IDA Pro Plugin to Plugin Manager Format 2 | 3 | Migrating an IDA Pro plugin to the format supported by the IDA Pro Plugin Manager involves structuring the plugin as a Python package. This allows it to be installed via `pip` and discovered by the plugin manager using Python's entry points mechanism. 4 | 5 | This guide outlines the files that need to be moved, changed, or created, and describes the necessary modifications. 6 | 7 | ## Overview of Changes 8 | 9 | The core idea is to package your existing IDA plugin (typically a single `.py` file or a small collection of files) into a standard Python package structure. This involves: 10 | 11 | 1. Creating a dedicated directory for your plugin. 12 | 2. Moving your plugin's Python code into this directory. 13 | 3. Adding a `pyproject.toml` file to define the package metadata and entry points. 14 | 4. Adding or updating a `README.md` file for documentation. 15 | 5. Optionally, creating a GitHub Actions workflow for automated building and publishing to PyPI. 16 | 17 | ## Files to be Moved, Changed, or Created 18 | 19 | Here's a breakdown of the files involved and the changes required: 20 | 21 | ### 1. Plugin's Main Python File (e.g., `original_plugin.py`) 22 | 23 | * **Move:** 24 | * This file is moved into a new dedicated directory for the plugin. For example, if your plugin is named `my_plugin`, you might create `plugins/my_plugin/` and move your file to `plugins/my_plugin/my_plugin.py`. 25 | * The filename should ideally match the intended Python module name (e.g., `tag_func.py` for a module named `tag_func`). 26 | 27 | ### 2. `pyproject.toml` (New File) 28 | 29 | * **Create:** This file must be created in the root of your plugin's new directory (e.g., `plugins/my_plugin/pyproject.toml`). 30 | * **Content/Changes:** This file uses TOML syntax to define project metadata and build information. 31 | 32 | ```toml 33 | [project] 34 | # Distribution name of the package on PyPI (e.g., "williballenthin-my-plugin-ida-plugin") 35 | # This is project-specific. 36 | name = "your-plugin-name-ida-plugin" 37 | 38 | # Version number of the plugin. Project-specific. 39 | version = "0.1.0" 40 | 41 | # List of authors. Project-specific. 42 | authors = [ 43 | {name = "Your Name", email = "your.email@example.com"}, 44 | ] 45 | 46 | # A brief description of the plugin. Project-specific. 47 | description = "An amazing IDA Pro plugin that does X, Y, and Z." 48 | 49 | # Specifies the README file. Typically "README.md". 50 | readme = "README.md" 51 | 52 | # License for the plugin. Project-specific. 53 | license = "Apache-2.0" # Or your chosen license 54 | 55 | # Minimum Python version required. Often similar across plugins. 56 | requires-python = ">=3.9" 57 | 58 | # List of runtime dependencies for the plugin. Project-specific. 59 | # If your plugin doesn't have external dependencies, this can be empty. 60 | dependencies = [] 61 | 62 | # This section is crucial for the IDA Pro Plugin Manager. 63 | # It tells the manager how to find and load your plugin. 64 | [project.entry-points.'idapro.plugins'] 65 | # 'idapython' is a conventional key. 66 | # "module_name" is the name of your Python module (e.g., "my_plugin" if your file is my_plugin.py). 67 | # This is project-specific. 68 | idapython = "my_plugin" 69 | 70 | # Standard build system configuration for setuptools. 71 | [build-system] 72 | requires = ["setuptools>=61.0"] 73 | build-backend = "setuptools.build_meta" 74 | 75 | # Configuration for setuptools. 76 | [tool.setuptools] 77 | # Specifies the Python module(s) to be included in the package. 78 | # This should match the "module_name" used in the entry point. 79 | # This is project-specific. 80 | py-modules = ["my_plugin"] 81 | ``` 82 | 83 | * **`[project]` section:** 84 | * `name`: The unique distribution name for PyPI. *Project-specific.* 85 | * `authors`: Your author details. *Project-specific.* 86 | * `description`: Plugin's purpose. *Project-specific.* 87 | * `version`: Plugin's version. *Project-specific.* 88 | * `readme`: Usually `"README.md"`. *Boilerplate structure.* 89 | * `license`: Your chosen license. *Project-specific.* 90 | * `requires-python`: Typically `">=3.9"` or similar. *Often boilerplate.* 91 | * `dependencies`: Any external Python libraries your plugin needs. *Project-specific.* 92 | * **`[project.entry-points.'idapro.plugins']` section:** 93 | * `idapython = "module_name"`: This is the key for discovery. `idapython` is the standard group. `module_name` is the name of your main plugin Python file (without the `.py` extension). *The group `idapro.plugins` and key `idapython` are boilerplate; the module name is project-specific.* 94 | * **`[build-system]` section:** 95 | * Defines build tool requirements (e.g., `setuptools`). *Generally boilerplate.* 96 | * **`[tool.setuptools]` section:** 97 | * `py-modules = ["module_name"]`: Lists the top-level Python modules to include. This should match the `module_name` from the entry point. *Project-specific module name.* 98 | 99 | ### 3. `README.md` (New or Updated File) 100 | 101 | * **Create/Update:** Place this file in your plugin's new directory (e.g., `plugins/my_plugin/README.md`). 102 | * **Content/Changes:** 103 | * **Description:** Explain what your plugin does, its features, and how to use it. *Project-specific content.* 104 | * **Installation:** Provide clear installation instructions. For plugins managed by the IDA Pro Plugin Manager, this usually involves `pip install your-package-name`. 105 | ```markdown 106 | ## Installation 107 | 108 | Assuming you have the [IDA Pro Plugin Manager](https://github.com/williballenthin/idawilli/tree/master/plugins/plugin-manager) (or a compatible setup that recognizes `idapro.plugins` entry points), install via pip: 109 | 110 | ```bash 111 | pip install your-plugin-name-ida-plugin 112 | ``` 113 | 114 | Make sure to use the `pip` associated with your IDAPython environment. 115 | ``` 116 | *The installation command structure is boilerplate; the package name is project-specific.* 117 | * **Publishing (Optional):** If you publish to PyPI, you might include a section linking to its PyPI page and the GitHub Actions workflow file. *Project-specific links and names.* 118 | 119 | ### 4. GitHub Actions Workflow File (e.g., `.github/workflows/publish-my-plugin.yml`) (New File, Optional) 120 | 121 | If you plan to publish your plugin to PyPI automatically, you can create a GitHub Actions workflow. 122 | 123 | * **Create:** This file goes into the `.github/workflows/` directory of your repository. 124 | * **Content/Changes:** This YAML file defines the CI/CD pipeline. 125 | 126 | ```yaml 127 | # .github/workflows/publish-my-plugin.yml 128 | name: publish your-plugin-name-ida-plugin to pypi 129 | 130 | on: 131 | # Example trigger: manual dispatch 132 | workflow_dispatch: 133 | # Example trigger: when a new tag starting with 'v' is pushed 134 | # push: 135 | # tags: 136 | # - 'v*' 137 | 138 | permissions: 139 | contents: read 140 | id-token: write # Required for PyPI trusted publishing 141 | 142 | jobs: 143 | pypi-publish: 144 | name: build and publish 145 | runs-on: ubuntu-latest 146 | environment: 147 | name: release 148 | permissions: 149 | id-token: write # Required for PyPI trusted publishing 150 | 151 | steps: 152 | - name: Checkout repository 153 | uses: actions/checkout@v4 154 | 155 | - name: Set up Python 156 | uses: actions/setup-python@v5 157 | with: 158 | python-version: '3.9' 159 | 160 | - name: Install dependencies (build tools) 161 | run: | 162 | python -m pip install --upgrade pip 163 | pip install setuptools build 164 | 165 | - name: Build package 166 | run: | 167 | # Navigate to your plugin's directory if pyproject.toml is not at the repo root 168 | cd plugins/my_plugin 169 | python -m build 170 | 171 | - name: Publish package to PyPI 172 | uses: pypa/gh-action-pypi-publish@release/v1 173 | with: 174 | packages-dir: plugins/my_plugin/dist # Path to the built package(s) 175 | 176 | - name: Upload package artifacts 177 | uses: actions/upload-artifact@v4 178 | with: 179 | name: python-package 180 | path: plugins/my_plugin/dist/* 181 | ``` 182 | * `name`: A descriptive name for the workflow. *Project-specific.* 183 | * `jobs.pypi-publish.steps`: 184 | * `Build package`: 185 | * `cd path/to/plugin_dir`: Navigates to your plugin's directory. *Project-specific path.* 186 | * `Publish package to PyPI`: 187 | * `packages-dir: path/to/plugin_dir/dist`: Specifies where the built package (`.tar.gz`, `.whl`) is located. *Project-specific path.* 188 | * `Upload package artifacts`: 189 | * `path: path/to/plugin_dir/dist/*`: Specifies the built artifacts to upload. *Project-specific path.* 190 | 191 | ## Summary of Data Types 192 | 193 | When migrating, some information will be standard (boilerplate), while other parts are unique to your plugin. 194 | 195 | ### Typically Same (Boilerplate) 196 | 197 | * The overall structure of `pyproject.toml` (presence of `[project]`, `[project.entry-points.'idapro.plugins']`, `[build-system]`, `[tool.setuptools]`). 198 | * Common keys in `pyproject.toml` like `readme = "README.md"`, `requires-python`. 199 | * The `[build-system]` section in `pyproject.toml` is usually standard for `setuptools`. 200 | * The entry point group `idapro.plugins` and the conventional key `idapython` used within it. 201 | * The general structure of a GitHub Actions workflow for building and publishing a Python package (checkout, setup Python, install build tools, build command, PyPI publish action). 202 | 203 | ### Specific to the Project 204 | 205 | * **Plugin Code:** The actual Python code and logic of your plugin. 206 | * **`pyproject.toml` values:** 207 | * `name` (the PyPI package name) 208 | * `authors` 209 | * `description` 210 | * `version` 211 | * `license` 212 | * `dependencies` (if any) 213 | * The Python `module_name` used in `[project.entry-points.'idapro.plugins']` (e.g., `idapython = "my_plugin"`) and in `[tool.setuptools]` (e.g., `py-modules = ["my_plugin"]`). 214 | * **`README.md` content:** Specific details about your plugin, its features, and advanced usage. 215 | * **GitHub Actions Workflow:** 216 | * The workflow `name`. 217 | * Specific paths to your plugin directory used in `cd` commands, `packages-dir` for publishing, and artifact `path`. 218 | * **PyPI Package Name:** The unique name under which your plugin is registered on PyPI. 219 | * **Module/File Names:** The name of your plugin's Python file(s) and the corresponding module name. 220 | 221 | By following these steps, you can adapt your existing IDA Pro plugin to be compatible with the IDA Pro Plugin Manager, making it easier to distribute, install, and manage. 222 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/basic-ida-plugin/README.md: -------------------------------------------------------------------------------- 1 | # basic-ida-plugin 2 | 3 | An example IDA plugin. 4 | 5 | 6 | ## publishing 7 | 8 | Published to PyPI as [basic-ida-plugin](https://pypi.org/project/basic-ida-plugin/) via the GH Action workflow [publish-basic-ida-plugin.yml](/.github/workflows/publish-basic-ida-plugin.yml). 9 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/basic-ida-plugin/hello.py: -------------------------------------------------------------------------------- 1 | import ida_idaapi 2 | 3 | 4 | class hello_plugmod_t(ida_idaapi.plugmod_t): 5 | def run(self, arg): 6 | print("Hello world from Python!") 7 | return 0 8 | 9 | 10 | class hello_plugin_t(ida_idaapi.plugin_t): 11 | flags = ida_idaapi.PLUGIN_UNL | ida_idaapi.PLUGIN_MULTI 12 | comment = "This is an example Python plugin (comment)" 13 | help = "This is an example Python plugin" 14 | wanted_name = "Example Python plugin" 15 | wanted_hotkey = "" 16 | 17 | def init(self): 18 | return hello_plugmod_t() 19 | 20 | 21 | def PLUGIN_ENTRY(): 22 | return hello_plugin_t() 23 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/basic-ida-plugin/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "basic-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "Example IDA Plugin" 7 | version = "0.1.0" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "hello" 15 | 16 | [tool.setuptools] 17 | # This places `hello.py` directly into `site-packages` (gross), 18 | # and lets you do `import hello` (nice). 19 | # So, I don't love this, but it works. 20 | # 21 | # Too bad this doesn't get picked up from the entry points above. 22 | py-modules = ["hello"] -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/multifile-ida-plugin/README.md: -------------------------------------------------------------------------------- 1 | # multifile-ida-plugin 2 | 3 | An example IDA plugin that uses multiple files. 4 | 5 | ``` 6 | multifile-ida-plugin 7 | ├── pyproject.toml 8 | ├── README.md 9 | └── src 10 | └── multifile_ida_plugin 11 | ├── __init__.py 12 | └── plugin 13 | └── __init__.py 14 | ``` 15 | 16 | There is a library `multifile_ida_plugin` with arbitrary code 17 | (in this case, a function `multifile_ida_plugin.hello`). 18 | 19 | ```py 20 | def hello(): 21 | print("hello from Python (multifile)") 22 | ``` 23 | 24 | Then, there's an IDA plugin found in `multifile_ida_plugin.plugin` 25 | which corresponds to the file `src/multifile_ida_plugin/plugin/__init__.py` 26 | and contains the `PLUGIN_ENTRY` function expected by IDA. 27 | 28 | 29 | ```py 30 | import idaapi 31 | import multifile_ida_plugin 32 | 33 | class multifile_plugmod_t(idaapi.plugmod_t): 34 | def run(self, arg): 35 | multifile_ida_plugin.hello() 36 | return 0 37 | 38 | class multifile_plugin_t(idaapi.plugin_t): 39 | ... 40 | def init(self): 41 | return multifile_plugmod_t() 42 | 43 | 44 | def PLUGIN_ENTRY(): 45 | return multifile_plugin_t() 46 | ``` 47 | 48 | The plugin invokes a routine from the library code, demonstrating how not all of the plugin code 49 | has to be bundled into the primary plugin file. 50 | 51 | The `pyproject.toml` shows how to specify the location of the plugin: 52 | 53 | ```toml 54 | [project.entry-points.'idapro.plugins'] 55 | idapython = "multifile_ida_plugin.plugin" 56 | ``` 57 | 58 | ## publishing 59 | 60 | Published to PyPI as [multifile-ida-plugin](https://pypi.org/project/multifile-ida-plugin/) via the GH Action workflow [publish-multifile-ida-plugin.yml](/.github/workflows/publish-multifile-ida-plugin.yml). 61 | 62 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/multifile-ida-plugin/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "multifile-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "Example IDA Plugin with multiple files" 7 | version = "0.1.0" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "multifile_ida_plugin.plugin" # refers to src/multifile_ida_plugin/plugin/__init__.py 15 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/multifile-ida-plugin/src/multifile_ida_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | print("Hello from Python (multifile)!") 3 | -------------------------------------------------------------------------------- /plugins/plugin-manager/examples/multifile-ida-plugin/src/multifile_ida_plugin/plugin/__init__.py: -------------------------------------------------------------------------------- 1 | import idaapi 2 | import multifile_ida_plugin 3 | 4 | 5 | class multifile_plugmod_t(idaapi.plugmod_t): 6 | def run(self, arg): 7 | multifile_ida_plugin.hello() 8 | return 0 9 | 10 | 11 | class multifile_plugin_t(idaapi.plugin_t): 12 | flags = idaapi.PLUGIN_UNL | idaapi.PLUGIN_MULTI 13 | comment = "This is an example Python plugin (multifile) (comment)" 14 | help = "This is an example Python plugin (multifile)" 15 | wanted_name = "Example Python plugin (multifile)" 16 | wanted_hotkey = "" 17 | 18 | def init(self): 19 | return multifile_plugmod_t() 20 | 21 | 22 | def PLUGIN_ENTRY(): 23 | return multifile_plugin_t() 24 | -------------------------------------------------------------------------------- /plugins/plugin-manager/idapro_plugin_manager/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | import platform 4 | 5 | if sys.version_info < (3, 10): 6 | # once we drop support for Python 3.9, 7 | # remove this and the dependency on `importlib_metadata`. 8 | import importlib_metadata 9 | import importlib_resources 10 | else: 11 | import importlib.metadata as importlib_metadata 12 | import importlib.resources as importlib_resources 13 | 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | def get_current_target_triple() -> str: 19 | """ 20 | Generates the current target triple based on the OS and architecture. 21 | The format aims to be similar to "arch-vendor-os". 22 | 23 | Supports: 24 | - OS: Linux, macOS (Darwin), Windows 25 | - Architectures: x86_64 (amd64), aarch64 (arm64) 26 | 27 | Returns: 28 | str: The target triple string (e.g., "aarch64-apple-darwin", "x86_64-unknown-linux", "x86_64-pc-windows"). 29 | 30 | Raises: 31 | ValueError: If the OS or architecture is not supported or recognized. 32 | """ 33 | system_name = platform.system() 34 | machine_name = platform.machine() 35 | 36 | arch = "" 37 | machine_lower = machine_name.lower() 38 | if machine_lower in ("amd64", "x86_64"): 39 | arch = "x86_64" 40 | elif machine_lower in ("arm64", "aarch64"): 41 | arch = "aarch64" 42 | else: 43 | raise ValueError(f"Unsupported architecture: {machine_name}") 44 | 45 | os_vendor_part = "" 46 | if system_name == "Darwin": 47 | os_vendor_part = "apple-darwin" 48 | elif system_name == "Linux": 49 | os_vendor_part = "unknown-linux" 50 | elif system_name == "Windows": 51 | os_vendor_part = "pc-windows" 52 | else: 53 | raise ValueError(f"Unsupported operating system: {system_name}") 54 | 55 | return f"{arch}-{os_vendor_part}" 56 | 57 | 58 | def install(): 59 | """ 60 | Load all registered IDA Pro plugins. 61 | 62 | Plugins are registered by adding an entry point to the `idapro.plugins` group, 63 | which is done in a Python package's setuptools or pyproject.toml configuration. 64 | 65 | For example, consider a simple plugin package like: 66 | 67 | basic-ida-plugin/ 68 | ├── hello.py 69 | └── pyproject.toml 70 | 71 | with the pyproject.toml contents: 72 | 73 | [project] 74 | name = "basic-ida-plugin" 75 | ... 76 | 77 | [project.entry-points.'idapro.plugins'] 78 | idapython = 'hello' 79 | 80 | and hello.py contents: 81 | 82 | import idaapi 83 | 84 | class hello_plugmod_t(idaapi.plugmod_t): 85 | def run(self, arg): 86 | print("Hello world! (py)") 87 | return 0 88 | 89 | class hello_plugin_t(idaapi.plugin_t): 90 | flags = idaapi.PLUGIN_UNL | idaapi.PLUGIN_MULTI 91 | comment = "This is a comment" 92 | help = "This is help" 93 | wanted_name = "Hello Python plugin" 94 | wanted_hotkey = "Alt-F8" 95 | 96 | def init(self): 97 | print("hello from init") 98 | return hello_plugmod_t() 99 | 100 | def PLUGIN_ENTRY(): 101 | return hello_plugin_t() 102 | """ 103 | # keep this import lazy so the top level module can be imported 104 | # without being inside IDA (such as in tests). 105 | import ida_loader 106 | 107 | current_target = get_current_target_triple() 108 | logger.info("current target: %s", current_target) 109 | plugins = list(importlib_metadata.entry_points(group="idapro.plugins")) 110 | 111 | for plugin in plugins: 112 | # the name of the plugin, from the project name, like `basic-ida-plugin` here: 113 | # 114 | # [project] 115 | # name = "basic-ida-plugin" 116 | if not plugin.dist: 117 | logger.warning("missing dist: %s", plugin) 118 | continue 119 | name = plugin.dist.name 120 | 121 | # `plugin.name` is the key of an entry point item like `idapython` here: 122 | # 123 | # [project.entry-points.'idapro.plugins'] 124 | # idapython = 'hello' 125 | target = plugin.name 126 | 127 | if target == "idapython": 128 | # load an IDAPython-based plugin 129 | logger.debug("loading Python plugin: %s", name) 130 | 131 | # Import the Python module that contains the plugin. 132 | # This is a standard Python importlib-level operation to load the code. 133 | # It invokes anything at the top level of the module, but 134 | # but it doesn't call `PLUGIN_ENTRY`, which is handled by `load_plugin` below. 135 | try: 136 | mod = plugin.load() 137 | except ImportError as e: 138 | logger.warning("failed to load: %s: %s", name, e, exc_info=True) 139 | continue 140 | 141 | # Path to the plugin, which is typically somewhere in the site-packages directory. 142 | # *Technically* a Python module doesn't have to be backed by a file, but this is rare, 143 | # so let's assume/require this for the plugin manager. 144 | path = mod.__file__ 145 | 146 | # Now load the plugin using IDA's infrastructure (invoking `PLUGIN_ENTRY`). 147 | try: 148 | logger.debug("loading_plugin: %s", str(path)) 149 | ida_loader.load_plugin(path) 150 | except Exception as e: # pylint: disable=broad-except 151 | logger.warning("failed to load_plugin: %s: %s", name, e, exc_info=True) 152 | continue 153 | 154 | logger.info("loaded: %s", name) 155 | 156 | elif target == current_target: 157 | # load a native plugin 158 | logger.debug("loading native plugin: %s", name) 159 | 160 | # This is like "native_ida_plugin" for the spec "native_ida_plugin:mysample" 161 | module_path = importlib_resources.files(plugin.module) 162 | 163 | extension: str 164 | if target.endswith("-darwin"): 165 | extension = ".dylib" 166 | elif target.endswith("-linux"): 167 | extension = ".so" 168 | elif target.endswith("-windows"): 169 | extension = ".dll" 170 | else: 171 | logger.warning("unexpected target: %s", target) 172 | continue 173 | 174 | # This is like "mysample" for the spec "native_ida_plugin:mysample" 175 | plugin_file_name = plugin.attr 176 | 177 | # Like: "native_ida_plugin/mysample.so" 178 | plugin_path = str(module_path / plugin_file_name) + extension 179 | 180 | # Load the plugin using IDA's infrastructure (invoking `PLUGIN_ENTRY`). 181 | try: 182 | logger.debug("loading_plugin: %s", plugin_path) 183 | ida_loader.load_plugin(plugin_path) 184 | except Exception as e: # pylint: disable=broad-except 185 | logger.warning("failed to load_plugin: %s: %s", name, e, exc_info=True) 186 | continue 187 | 188 | logger.info("loaded: %s", name) 189 | 190 | else: 191 | logger.warning("unexpected target: %s", target) 192 | -------------------------------------------------------------------------------- /plugins/plugin-manager/img/pypi-trusted-publishing-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/plugins/plugin-manager/img/pypi-trusted-publishing-configuration.png -------------------------------------------------------------------------------- /plugins/plugin-manager/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "idapro-plugin-manager" 3 | version = "0.2.0" 4 | description = "Plugin Manager for IDA Pro" 5 | authors = [ 6 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 7 | ] 8 | readme = {file = "README.md", content-type = "text/markdown"} 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [ 12 | # importlib.metadata available in Python 3.10 13 | # so we use this backport until we drop support for Python 3.9. 14 | "importlib-metadata>=8.5.0", 15 | "importlib-resources>=6.5.2", 16 | 17 | # for ippm cli tool 18 | "packaging>=24.0", 19 | "platformdirs>=4.0.0", 20 | "requests>=2.30.0", 21 | "requests-cache>=1.2.0", 22 | "rich>=13.0.0", 23 | ] 24 | keywords = ["reverse engineering", "ida pro", "idapro", "plugin manager", "plugins"] 25 | classifiers = [ 26 | "Development Status :: 3 - Alpha", 27 | "Intended Audience :: Developers", 28 | "Intended Audience :: Information Technology", 29 | "Natural Language :: English", 30 | "Programming Language :: Python :: 3", 31 | "Topic :: Security", 32 | ] 33 | 34 | [build-system] 35 | requires = ["setuptools", "setuptools-scm"] 36 | build-backend = "setuptools.build_meta" 37 | 38 | [tool.setuptools.packages.find] 39 | include = ["idapro_plugin_manager*"] 40 | namespaces = false 41 | 42 | [project.optional-dependencies] 43 | dev = [ 44 | "types-requests>=2.32.0.20250602", 45 | ] 46 | build = [ 47 | # Dev and build dependencies are not relaxed because 48 | # we want all developer environments to be consistent. 49 | # These dependencies are not used in production environments 50 | # and should not conflict with other libraries/tooling. 51 | "setuptools==78.1.1", 52 | "build==1.2.2" 53 | ] 54 | 55 | [project.scripts] 56 | ippm = "idapro_plugin_manager.__main__:main" 57 | -------------------------------------------------------------------------------- /plugins/tag_func/README.md: -------------------------------------------------------------------------------- 1 | # Tag Function IDA Pro Plugin 2 | 3 | IDA Pro plugin for tagging functions into folders. 4 | 5 | ## Installation 6 | 7 | Assuming you have the [IDA Pro Plugin Manager](https://github.com/williballenthin/idawilli/tree/master/plugins/plugin-manager/), install via pip: 8 | 9 | ```bash 10 | pip install williballenthin-tag-func-ida-plugin 11 | ``` 12 | 13 | Make sure to use the pip from your IDAPython installation. -------------------------------------------------------------------------------- /plugins/tag_func/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "williballenthin-tag-func-ida-plugin" 3 | authors = [ 4 | {name = "Willi Ballenthin", email = "willi.ballenthin@gmail.com"}, 5 | ] 6 | description = "IDA Pro plugin for tagging functions into folders" 7 | version = "0.1.0" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.entry-points.'idapro.plugins'] 14 | idapython = "tag_func" 15 | 16 | [tool.setuptools] 17 | py-modules = ["tag_func"] -------------------------------------------------------------------------------- /plugins/tag_func/tag_func.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Iterator, List, Optional, Tuple 3 | 4 | import ida_name 5 | import ida_funcs 6 | import ida_idaapi 7 | import ida_kernwin 8 | import ida_dirtree 9 | 10 | from ida_dirtree import dirtree_t 11 | 12 | logger = logging.getLogger("tag_func") 13 | 14 | 15 | def dirtree_find(dirtree: dirtree_t, pattern) -> Iterator[ida_dirtree.dirtree_cursor_t]: 16 | """ 17 | enumerate the matches for the given pattern against the given dirtree. 18 | this is just a Pythonic helper over the SWIG-generated routines. 19 | """ 20 | # pattern format: 21 | # "*" for all in current directory, does not recurse 22 | # "/" for root directory 23 | # "/sub_410000" for item by name 24 | # "/foo" for directory by name, no trailing slash 25 | # "/foo/*" for path prefix 26 | # does not recurse beyond the prefix path 27 | # matches "/foo/sub_401000" and but not "/foo/bar/sub_4010005" 28 | # "/foo/sub_*" for path prefix (matches "/foo/sub_401000") 29 | # "*main" for suffix (matches "/_main" because leading / is implied) 30 | # "*mai*" for substring (matches "/_main" and "/_main_0" because leading / is implied) 31 | # 32 | # wildcards only seem to match within path components 33 | # does *not* work: 34 | # "/*/sub_401000" 35 | # "*/sub_401000" 36 | # "*" 37 | # 38 | # to search by name, i guess use pattern "*" and check get_entry_name 39 | ff = ida_dirtree.dirtree_iterator_t() 40 | ok = dirtree.findfirst(ff, pattern) 41 | while ok: 42 | yield ff.cursor 43 | ok = dirtree.findnext(ff) 44 | 45 | 46 | def dirtree_join(*parts: list[str]) -> str: 47 | return "/".join(parts) 48 | 49 | 50 | def dirtree_walk(dirtree: dirtree_t, top: str) -> Iterator[Tuple[str, List[str], List[str]]]: 51 | """ 52 | like os.walk over the given dirtree. 53 | 54 | yields tuples: (root, [dirs], [files]) 55 | use dirtree_join(*parts) to join root and dir/file entry: 56 | 57 | # print all files 58 | for root, dirs, files in dirtree_walk(func_dir, "/"): 59 | for file in files: 60 | print(dirtree_join(root, file)) 61 | """ 62 | top = top.rstrip("/") 63 | directories = [top] 64 | 65 | while len(directories) > 0: 66 | directory = directories.pop(0) 67 | 68 | dirs = [] 69 | files = [] 70 | 71 | for cursor in dirtree_find(dirtree, f"{directory}/*"): 72 | dirent = dirtree.resolve_cursor(cursor) 73 | name = dirtree.get_entry_name(dirent) 74 | 75 | if dirent.isdir: 76 | dirs.append(name) 77 | directories.append(dirtree_join(directory, name)) 78 | else: 79 | files.append(name) 80 | 81 | yield (directory, dirs, files) 82 | 83 | 84 | def find_function_dirtree_path(va: int) -> Optional[str]: 85 | """ 86 | given the address of a function 87 | find its absolute path within the function dirtree. 88 | """ 89 | func_dir: dirtree_t = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS) 90 | 91 | name = ida_name.get_name(va) 92 | if not name: 93 | return None 94 | 95 | for root, _, files in dirtree_walk(func_dir, "/"): 96 | for file in files: 97 | if file == name: 98 | return dirtree_join(root, file) 99 | 100 | return None 101 | 102 | 103 | def dirtree_mkdirs(dirtree: dirtree_t, path: str): 104 | parts = path.split("/") 105 | 106 | for i in range(2, len(parts) + 1): 107 | prefix = "/".join(parts[:i]) 108 | 109 | if not dirtree.isdir(prefix): 110 | e = dirtree.mkdir(prefix) 111 | if e != ida_dirtree.DTE_OK: 112 | logger.error("error: %s", ida_dirtree.dirtree_t_errstr(e)) 113 | return e 114 | 115 | return ida_dirtree.DTE_OK 116 | 117 | 118 | def set_tagged_func_cmt(tag: str, va: int, cmt: str, repeatable: bool): 119 | func = ida_funcs.get_func(va) 120 | existing = (ida_funcs.get_func_cmt(func, repeatable) or "").strip() 121 | 122 | prefix = f"{tag}: " 123 | line = f"{prefix}{cmt}" 124 | 125 | if prefix in existing: 126 | rest = existing.partition(prefix)[2].partition("\n")[0] 127 | new = existing.replace(f"{prefix}{rest}", line) 128 | elif existing == "": 129 | new = line 130 | else: 131 | new = existing + f"\n{line}" 132 | 133 | ida_funcs.set_func_cmt(func, new, repeatable) 134 | 135 | 136 | def set_func_folder_cmt(va: int, folder: str): 137 | set_tagged_func_cmt("📁", va, folder, True) 138 | 139 | 140 | def sync_func_folder_cmts(): 141 | func_dir: dirtree_t = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS) 142 | for root, _, files in dirtree_walk(func_dir, "/"): 143 | if root == "/" or root == "": 144 | continue 145 | 146 | tag = root.lstrip("/") 147 | for file in files: 148 | va = ida_name.get_name_ea(ida_idaapi.BADADDR, file) 149 | if va == ida_idaapi.BADADDR: 150 | continue 151 | 152 | set_func_folder_cmt(va, tag) 153 | 154 | 155 | def main(): 156 | va = ida_kernwin.get_screen_ea() 157 | f = ida_funcs.get_func(va) 158 | if not f: 159 | logger.error("function not found: 0x%x", va) 160 | return 161 | 162 | path = find_function_dirtree_path(f.start_ea) 163 | if not path: 164 | logger.error("function directory entry not found: 0x%x", f.start_ea) 165 | return 166 | 167 | func_dir: dirtree_t = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS) 168 | 169 | dirent = func_dir.resolve_path(path) 170 | name = func_dir.get_entry_name(dirent) 171 | existing_tag = path[: -(len("/") + len(name))].lstrip("/") 172 | 173 | # ask_str(defval, hist, prompt) -> PyObject * 174 | # I'm not sure what "history id" does. 175 | tag = ida_kernwin.ask_str(existing_tag, 69, "tag:") 176 | if not tag: 177 | return 178 | 179 | tag_path = f"/{tag}" 180 | if not func_dir.isdir(tag_path): 181 | logger.info("creating tag: %s", tag) 182 | 183 | e = dirtree_mkdirs(func_dir, tag_path) 184 | if e != ida_dirtree.DTE_OK: 185 | logger.error("error: failed to create tag: %s", tag) 186 | return 187 | 188 | else: 189 | logger.debug("tag exists: %s", tag) 190 | 191 | src_path = path 192 | src_dirent = func_dir.resolve_path(src_path) 193 | src_name = func_dir.get_entry_name(src_dirent) 194 | 195 | dst_name = src_name 196 | dst_path = f"{tag_path}/{dst_name}" 197 | 198 | if src_path == dst_path: 199 | logger.info("skipping move to itself") 200 | return 201 | 202 | logger.info("moving %s from %s to %s", src_name, src_path, dst_path) 203 | e = func_dir.rename(src_path, dst_path) 204 | if e != ida_dirtree.DTE_OK: 205 | logger.error("error: %s", ida_dirtree.dirtree_t_errstr(e)) 206 | return 207 | 208 | set_func_folder_cmt(f.start_ea, tag) 209 | 210 | 211 | class TagFunctionPlugin(ida_idaapi.plugin_t): 212 | flags = ida_idaapi.PLUGIN_UNL | ida_idaapi.PLUGIN_MULTI 213 | comment = "Quickly organize functions into tags via hotkey" 214 | help = "Quickly organize functions into tags via hotkey" 215 | wanted_name = "Tag Function" 216 | wanted_hotkey = "Z" 217 | 218 | def init(self): 219 | sync_func_folder_cmts() 220 | return ida_idaapi.PLUGIN_OK 221 | 222 | def run(self, _arg): 223 | main() 224 | return True 225 | 226 | 227 | def PLUGIN_ENTRY(): 228 | return TagFunctionPlugin() 229 | 230 | 231 | if __name__ == "__main__": 232 | sync_func_folder_cmts() 233 | main() 234 | -------------------------------------------------------------------------------- /scripts/add_segment/add_segment.py: -------------------------------------------------------------------------------- 1 | ''' 2 | IDAPython plugin that adds the contents of a file as a new segment in an existing idb. 3 | Prompts the user for: 4 | - file path 5 | - segment name 6 | - segment starting offset 7 | 8 | Useful for reversing engineering packed software and shellcode. 9 | 10 | Author: Willi Ballenthin 11 | Licence: Apache 2.0 12 | ''' 13 | import logging 14 | from collections import namedtuple 15 | 16 | import idc 17 | import idaapi 18 | import idautils 19 | 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class BadInputError(Exception): 25 | pass 26 | 27 | 28 | Segment = namedtuple('SegmentBuffer', ['path', 'name', 'addr']) 29 | 30 | 31 | def prompt_for_segment(): 32 | ''' :returns: a Segment instance, or raises BadInputError ''' 33 | class MyForm(idaapi.Form): 34 | def __init__(self): 35 | idaapi.Form.__init__(self, """STARTITEM 0 36 | add segment by buffer 37 | 38 | <##buffer path:{path}> 39 | <##segment name:{name}> 40 | <##segment start address:{addr}> 41 | """, 42 | { 43 | 'path': idaapi.Form.FileInput(open=True), 44 | 'name': idaapi.Form.StringInput(), 45 | 'addr': idaapi.Form.NumericInput(tp=Form.FT_ADDR), 46 | }) 47 | 48 | def OnFormChange(self, fid): 49 | return 1 50 | 51 | f = MyForm() 52 | f.Compile() 53 | f.path.value = "" 54 | f.name.value = "" 55 | f.addr.value = 0x0 56 | ok = f.Execute() 57 | if ok != 1: 58 | raise BadInputError('user cancelled') 59 | 60 | path = f.path.value 61 | if path == "" or path is None: 62 | raise BadInputError('bad path provided') 63 | 64 | if not os.path.exists(path): 65 | raise BadInputError('file doesn\'t exist') 66 | 67 | name = f.name.value 68 | if name == "" or name is None: 69 | raise BadInputError('bad name provided') 70 | 71 | addr = f.addr.value 72 | f.Free() 73 | return Segment(path, name, addr) 74 | 75 | 76 | def main(argv=None): 77 | if argv is None: 78 | argv = sys.argv[:] 79 | 80 | try: 81 | seg = prompt_for_segment() 82 | except BadInputError: 83 | logger.error('bad input, exiting...') 84 | return -1 85 | 86 | with open(seg.path, 'rb') as f: 87 | buf = f.read() 88 | 89 | seglen = len(buf) 90 | if seglen % 0x1000 != 0: 91 | seglen = seglen + (0x1000 - (seglen % 0x1000)) 92 | 93 | if not idc.AddSeg(seg.addr, seg.addr + seglen, 0, 1, 0, idaapi.scPub): 94 | logger.error('failed to add segment: 0x%x', seg.addr) 95 | return -1 96 | 97 | if not idc.set_segm_name(seg.addr, seg.name): 98 | logger.warning('failed to rename segment: %s', seg.name) 99 | 100 | if not idc.set_segm_class(seg.addr, 'CODE'): 101 | logger.warning('failed to set segment class CODE: %s', seg.name) 102 | 103 | if not idc.set_segm_attr(seg.addr, SEGATTR_ALIGN, idc.saRelPara): 104 | logger.warning('failed to align segment: %s', seg.name) 105 | 106 | ida_bytes.patch_bytes(seg.addr, buf) 107 | 108 | 109 | class AddSegmentPlugin(idaapi.plugin_t): 110 | flags = idaapi.PLUGIN_KEEP 111 | comment = "Add a segment to an IDA .idb from a file." 112 | 113 | help = "Add a segment to an IDA .idb from a file." 114 | wanted_name = "AddSegment" 115 | wanted_hotkey = "Alt-F8" 116 | 117 | def init(self): 118 | return idaapi.PLUGIN_OK 119 | 120 | def run(self, arg): 121 | main() 122 | 123 | def term(self): 124 | pass 125 | 126 | 127 | def PLUGIN_ENTRY(): 128 | return AddSegmentPlugin() 129 | 130 | 131 | if __name__ == '__main__': 132 | logging.basicConfig(level=logging.DEBUG) 133 | main() 134 | -------------------------------------------------------------------------------- /scripts/add_segment/readme.md: -------------------------------------------------------------------------------- 1 | # add_segment 2 | 3 | IDAPython plugin that adds the contents of a file as a new segment in an existing idb. 4 | Prompts the user for: 5 | - file path 6 | - segment name 7 | - segment starting offset 8 | 9 | Useful for reversing engineering packed software and shellcode. 10 | 11 | ## installation 12 | 13 | none 14 | 15 | ## usage 16 | 17 | invoke the script via `File->Script file...`. 18 | -------------------------------------------------------------------------------- /scripts/allocate_rwx_page.py: -------------------------------------------------------------------------------- 1 | import ida_kernwin 2 | 3 | import idawilli.dbg 4 | 5 | 6 | def main(): 7 | size = ida_kernwin.ask_long(0x1000, "size of allocation") 8 | if not size: 9 | return 10 | 11 | ptr = idawilli.dbg.allocate_rwx(size) 12 | print('allocated 0x%x bytes at 0x%x' % (size, ptr)) 13 | 14 | 15 | main() -------------------------------------------------------------------------------- /scripts/color/color.py: -------------------------------------------------------------------------------- 1 | ''' 2 | IDAPython script that colors instructions. 3 | 4 | Author: Willi Ballenthin 5 | Licence: Apache 2.0 6 | ''' 7 | import logging 8 | from collections import namedtuple 9 | 10 | import ida_settings 11 | 12 | import idc 13 | import idaapi 14 | import idautils 15 | import ida_ua 16 | import ida_bytes 17 | import ida_segment 18 | 19 | 20 | logger = logging.getLogger(__name__) 21 | settings = ida_settings.IDASettings('idawilli.color') 22 | 23 | CALL_COLOR = settings.get('colors.instructions.call', 0xD7C2C0) # blueish 24 | ENCRYPT_COLOR = settings.get('colors.behaviors.encrypt', 0xC0C2D7) # redish 25 | ANTIANALYSIS_COLOR = settings.get( 26 | 'colors.behaviors.anti-analysis', 0xC0C2D7) # redish 27 | 28 | 29 | Segment = namedtuple('Segment', ['start', 'end', 'name']) 30 | 31 | 32 | def enum_segments(): 33 | for ea in idautils.Segments(): 34 | seg = ida_segment.getseg(ea) 35 | yield Segment(seg.start_ea, seg.end_ea, seg.name) 36 | 37 | 38 | def enum_heads(): 39 | for segment in enum_segments(): 40 | for head in idautils.Heads(segment.start, segment.end): 41 | yield head 42 | 43 | 44 | def color_head(ea): 45 | flags = ida_bytes.get_flags(ea) 46 | if not ida_bytes.is_code(flags): 47 | return 48 | 49 | mnem = ida_ua.print_insn_mnem(ea) 50 | if mnem == 'call': 51 | logger.debug('call: 0x%x', ea) 52 | idc.set_color(ea, idc.CIC_ITEM, CALL_COLOR) 53 | elif mnem == 'xor': 54 | if idc.get_operand_value(ea, 0) != idc.get_operand_value(ea, 1): 55 | logger.debug('non-zero xor: 0x%x', ea) 56 | idc.set_color(ea, idc.CIC_ITEM, ENCRYPT_COLOR) 57 | elif mnem in ('sdit', 'sgdt', 'sldt', 'smsw', 'str', 'in', 'cpuid'): 58 | logger.debug('anti-vm: 0x%x', ea) 59 | idc.set_color(ea, idc.CIC_ITEM, ANTIANALYSIS_COLOR) 60 | elif mnem == 'in': 61 | if idc.get_operand_value(ea, 0) in ("3", "2D"): 62 | logger.debug('anti-debug: 0x%x', ea) 63 | idc.set_color(ea, idc.CIC_ITEM, ANTIANALYSIS_COLOR) 64 | elif mnem in ('rdtsc', 'icebp'): 65 | logger.debug('anti-debug: 0x%x', ea) 66 | idc.set_color(ea, idc.CIC_ITEM, ANTIANALYSIS_COLOR) 67 | 68 | 69 | def main(argv=None): 70 | if argv is None: 71 | argv = sys.argv[:] 72 | 73 | for head in enum_heads(): 74 | color_head(head) 75 | 76 | 77 | if __name__ == '__main__': 78 | logging.basicConfig(level=logging.DEBUG) 79 | main() 80 | -------------------------------------------------------------------------------- /scripts/deob.py: -------------------------------------------------------------------------------- 1 | """ 2 | search for and patch out known opaque predicates within IDA Pro workspaces. 3 | 4 | just run the script and it will manipulate the open database. 5 | therefore, you should probably create a backup first. 6 | """ 7 | import logging 8 | from pprint import pprint 9 | 10 | import ida_idp 11 | import idautils 12 | import ida_auto 13 | import ida_bytes 14 | import ida_segment 15 | 16 | 17 | logger = logging.getLogger("deob") 18 | 19 | 20 | def nop_region(ea, size): 21 | """replace the given range with NOPs""" 22 | logger.debug("nopping region from 0x%x size 0x%x", ea, size) 23 | 24 | for i in range(ea, ea + size): 25 | ida_bytes.del_items(i) 26 | 27 | for i in range(ea, ea + size): 28 | ida_bytes.patch_byte(i, 0x90) 29 | 30 | ida_auto.auto_make_code(ea) 31 | 32 | 33 | def is_jump_plus_one(ea): 34 | """ 35 | like: 36 | 37 | .text:00401089 jmp short loc_40108C 38 | .text:00401089 ; --------------------------------------------------------------------------- 39 | .text:0040108B db 0BAh ; º 40 | .text:0040108C ; --------------------------------------------------------------------------- 41 | .text:0040108C 42 | .text:0040108C loc_40108C: ; CODE XREF: .text:00401089↑j 43 | .text:0040108C mov eax, 1 44 | """ 45 | insn = idautils.DecodeInstruction(ea) 46 | if insn is None: 47 | logger.debug("0x%x: failed to decode instruction", ea) 48 | return False, None 49 | 50 | if insn.get_canon_mnem() != "jmp": 51 | return False, None 52 | 53 | next_ea = insn.ea + insn.size 54 | if insn.ops[0].addr != next_ea + 1: 55 | return False, None 56 | 57 | return True, insn.size + 1 58 | 59 | 60 | def is_jmp_ret(ea): 61 | """ 62 | like: 63 | 64 | .text:004010C9 call $+5 65 | .text:004010CE add dword ptr [esp], 6 66 | .text:004010D2 retn 67 | .text:004010D2 ; --------------------------------------------------------------------------- 68 | .text:004010D3 db 7Dh ; } 69 | .text:004010D4 ; --------------------------------------------------------------------------- 70 | .text:004010D4 mov eax, 0 71 | """ 72 | insn = idautils.DecodeInstruction(ea) 73 | if insn is None: 74 | return False, None 75 | 76 | if insn.get_canon_mnem() != "call": 77 | return False, None 78 | 79 | next_ea = insn.ea + insn.size 80 | if insn.ops[0].addr != next_ea: 81 | return False, None 82 | 83 | insn = idautils.DecodeInstruction(next_ea) 84 | if insn is None: 85 | return False, None 86 | 87 | if insn.get_canon_mnem() != "add": 88 | return False, None 89 | 90 | delta = insn.ops[1].value 91 | target = next_ea + delta 92 | 93 | next_ea = insn.ea + insn.size 94 | 95 | insn = idautils.DecodeInstruction(next_ea) 96 | if insn is None: 97 | return False, None 98 | 99 | if insn.get_canon_mnem() != "retn": 100 | return False, None 101 | 102 | return True, target - ea 103 | 104 | 105 | def is_stc_jb(ea): 106 | """ 107 | like: 108 | 109 | .text:00401042 stc 110 | .text:00401043 jb short loc_401046 111 | .text:00401043 ; --------------------------------------------------------------------------- 112 | .text:00401045 db 0B1h ; ± 113 | .text:00401046 ; --------------------------------------------------------------------------- 114 | .text:00401046 115 | .text:00401046 loc_401046: ; CODE XREF: .text:00401043↑j 116 | .text:00401046 mov eax, 0 117 | """ 118 | insn = idautils.DecodeInstruction(ea) 119 | if insn is None: 120 | return False, None 121 | 122 | if insn.get_canon_mnem() != "stc": 123 | return False, None 124 | 125 | next_ea = insn.ea + insn.size 126 | 127 | insn = idautils.DecodeInstruction(next_ea) 128 | if insn is None: 129 | return False, None 130 | 131 | if insn.get_canon_mnem() != "jb": 132 | return False, None 133 | 134 | if insn.ops[0].addr != insn.ea + insn.size + 1: 135 | return False, None 136 | 137 | return True, 4 138 | 139 | 140 | def is_clc_jnb(ea): 141 | """ 142 | like: 143 | 144 | .text:0040139E clc 145 | .text:0040139F jnb short loc_4013A2 146 | .text:0040139F ; --------------------------------------------------------------------------- 147 | .text:004013A1 db 0Fh 148 | .text:004013A2 ; --------------------------------------------------------------------------- 149 | .text:004013A2 150 | .text:004013A2 loc_4013A2: ; CODE XREF: .text:0040139F↑j 151 | .text:004013A2 mov eax, 0x0 152 | """ 153 | insn = idautils.DecodeInstruction(ea) 154 | if insn is None: 155 | return False, None 156 | 157 | if insn.get_canon_mnem() != "clc": 158 | return False, None 159 | 160 | next_ea = insn.ea + insn.size 161 | 162 | insn = idautils.DecodeInstruction(next_ea) 163 | if insn is None: 164 | return False, None 165 | 166 | if insn.get_canon_mnem() != "jnb": 167 | return False, None 168 | 169 | if insn.ops[0].addr != insn.ea + insn.size + 1: 170 | return False, None 171 | 172 | return True, 4 173 | 174 | 175 | SEG_X = 0b001 176 | SEG_W = 0b010 177 | SEG_R = 0b100 178 | 179 | 180 | OBFUSCATIONS = [ 181 | is_jump_plus_one, 182 | is_jmp_ret, 183 | is_stc_jb, 184 | is_clc_jnb, 185 | ] 186 | 187 | 188 | if __name__ == "__main__": 189 | logging.basicConfig(level=logging.INFO) 190 | 191 | for segstart in idautils.Segments(): 192 | seg = ida_segment.getseg(segstart) 193 | if not (seg.perm & SEG_X): 194 | continue 195 | 196 | seg_name = ida_segment.get_segm_name(seg) 197 | 198 | for ea in range(seg.start_ea, seg.end_ea): 199 | for obfuscation in OBFUSCATIONS: 200 | is_ob, size = obfuscation(ea) 201 | if not is_ob: 202 | continue 203 | 204 | logger.info( 205 | "%s: 0x%x: found obfuscation(%s)", 206 | seg_name, 207 | ea, 208 | obfuscation.__name__, 209 | ) 210 | nop_region(ea, size) 211 | 212 | print("ok") 213 | -------------------------------------------------------------------------------- /scripts/find_ptrs/ida_find_ptrs.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | 4 | 5 | def enum_segments(): 6 | for segstart in idautils.Segments(): 7 | segend = idc.get_segm_end(segstart) 8 | segname = idc.get_segm_name(segstart) 9 | yield segstart, segend, segname 10 | 11 | 12 | def find_pointers(start, end): 13 | for va in range(start, end-0x4): 14 | ptr = idc.get_wide_dword(va) 15 | if idc.get_segm_start(ptr) == idc.BADADDR: 16 | continue 17 | 18 | yield va, ptr 19 | 20 | 21 | def is_head(va): 22 | return ida_bytes.is_head(idc.get_full_flags(va)) 23 | 24 | 25 | def get_head(va): 26 | if is_head(va): 27 | return va 28 | else: 29 | return idc.prev_head(va) 30 | 31 | 32 | def is_code(va): 33 | if is_head(va): 34 | flags = idc.get_full_flags(va) 35 | return ida_bytes.is_code(flags) 36 | else: 37 | head = get_head(va) 38 | return is_code(head) 39 | 40 | 41 | CACHED_STRINGS = list(idautils.Strings()) 42 | def is_in_string(va): 43 | for s in CACHED_STRINGS: 44 | if s.ea <= va < s.ea + s.length: 45 | return True 46 | return False 47 | 48 | 49 | def is_defined(va): 50 | pass 51 | 52 | 53 | def is_unknown(va): 54 | return ida_bytes.is_unknown(idc.get_full_flags(va)) 55 | 56 | 57 | def main(): 58 | for segstart, segend, segname in enum_segments(): 59 | if segname not in ('.text', '.data'): 60 | continue 61 | 62 | for src, dst in find_pointers(segstart, segend): 63 | if is_code(src): 64 | # ignore instructions like: 65 | # 66 | # call ds:__vbaGenerateBoundsError 67 | #print('code pointer: 0x%x -> 0x%x' % (src, dst)) 68 | continue 69 | 70 | if is_in_string(src): 71 | # for example, the following contains 0x444974 (a common valid offset): 72 | # 73 | # text:004245B0 aRequestid db 'requestID', 74 | # 75 | # enable or disable this behavior as you wish 76 | print('string pointer: 0x%x -> 0x%x' % (src, dst)) 77 | pass 78 | #continue 79 | 80 | print('pointer from 0x%x to 0x%x' % (src, dst)) 81 | 82 | if is_unknown(dst): 83 | print('destination unknown, making byte: 0x%x' % (dst)) 84 | ida_bytes.create_data(dst, FF_BYTE, 1, ida_idaapi.BADADDR) 85 | 86 | elif is_head(dst): 87 | # things are good 88 | pass 89 | 90 | else: 91 | # need to undefine head, and make byte 92 | head_va = get_head(dst) 93 | print('destination overlaps with head: 0x%x' % (head_va)) 94 | ida_bytes.del_items(head_va, dst - head_va) 95 | ida_bytes.create_data(head_va, FF_BYTE, 1, ida_idaapi.BADADDR) 96 | ida_bytes.create_data(dst, FF_BYTE, 1, ida_idaapi.BADADDR) 97 | 98 | ida_bytes.del_items(src, 4) 99 | ida_bytes.create_data(src, FF_DWORD, 4, ida_idaapi.BADADDR) 100 | # this doesn't seem to always work :-( 101 | idc.op_plain_offset(src, -1, 0) 102 | 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /scripts/find_ptrs/readme.md: -------------------------------------------------------------------------------- 1 | # ida_find_ptrs.py 2 | 3 | IDAPython script that scans through the .text section for values that could be pointers (32-bit). 4 | It marks these elements as such. 5 | This is useful when IDA Pro's auto-analysis leaves lots of unstructured data lying around. 6 | -------------------------------------------------------------------------------- /scripts/fix_ptr_ops/fix_ptr_operands.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import idautils 3 | import ida_offset 4 | 5 | reftype = ida_offset.get_default_reftype(next(idautils.Segments())) 6 | 7 | for segea in idautils.Segments(): 8 | for head in idautils.Heads(idc.get_segm_start(segea), idc.get_segm_end(segea)): 9 | if not ida_bytes.is_code(ida_bytes.get_full_flags(head)): 10 | continue 11 | 12 | for i in range(2): 13 | if idc.get_segm_start(idc.get_operand_value(head, i)) == idc.BADADDR: 14 | continue 15 | ida_offset.op_offset(head, i, reftype) 16 | 17 | print("ok") 18 | -------------------------------------------------------------------------------- /scripts/go/go_fixup_fptrs.py: -------------------------------------------------------------------------------- 1 | """ 2 | when IDA's auto-discovery of functions in 64-bit Windows Go executables fails, 3 | scan for global (.rdata) pointers into the code section (.text) and assume these are function pointers. 4 | """ 5 | import idc 6 | import ida_name 7 | import ida_auto 8 | import ida_bytes 9 | import idautils 10 | 11 | 12 | def enum_segments(): 13 | for segstart in idautils.Segments(): 14 | segend = idc.get_segm_end(segstart) 15 | segname = idc.get_segm_name(segstart) 16 | yield segstart, segend, segname 17 | 18 | 19 | def find_pointers(start, end): 20 | for va in range(start, end-0x8): 21 | ptr = ida_bytes.get_qword(va) 22 | if idc.get_segm_start(ptr) != idc.BADADDR: 23 | yield va, ptr, 8 24 | ptr = ida_bytes.get_dword(va) 25 | if idc.get_segm_start(ptr) != idc.BADADDR: 26 | yield va, ptr, 4 27 | 28 | 29 | def is_head(va): 30 | return ida_bytes.is_head(idc.get_full_flags(va)) 31 | 32 | 33 | def get_head(va): 34 | if is_head(va): 35 | return va 36 | else: 37 | return idc.prev_head(va) 38 | 39 | 40 | def is_code(va): 41 | if is_head(va): 42 | flags = idc.get_full_flags(va) 43 | return ida_bytes.is_code(flags) 44 | else: 45 | head = get_head(va) 46 | return is_code(head) 47 | 48 | 49 | def is_unknown(va): 50 | return ida_bytes.is_unknown(idc.get_full_flags(va)) 51 | 52 | 53 | def main(): 54 | for segstart, segend, segname in enum_segments(): 55 | if segname not in ('.rdata', 'UPX1'): 56 | continue 57 | 58 | print(segname) 59 | for src, dst, size in find_pointers(segstart, segend): 60 | if idc.get_segm_name(dst) not in (".text", "UPX0"): 61 | continue 62 | 63 | if is_code(dst): 64 | continue 65 | 66 | print("new function pointer: 0x%x -> 0x%x" % (src, dst)) 67 | 68 | ida_auto.auto_make_code(dst) 69 | ida_auto.auto_make_proc(dst) 70 | 71 | ida_bytes.del_items(src, size) 72 | ida_bytes.create_data(src, idc.FF_QWORD if size == 8 else idc.FF_DWORD, size, idc.BADADDR) 73 | # this doesn't seem to always work :-( 74 | idc.op_plain_offset(src, -1, 0) 75 | ida_name.set_name(src, "j_%s_%x" % (src, dst)) 76 | 77 | if __name__ == '__main__': 78 | main() 79 | -------------------------------------------------------------------------------- /scripts/go/go_fixup_global_strings.py: -------------------------------------------------------------------------------- 1 | """ 2 | find and annotate global references to string in 64-bit Windows Go executables. 3 | 4 | expect to see the pattern: 5 | 6 | .rdata: qword ptr string -> .rdata 7 | .rdata: qword size 8 | """ 9 | import idc 10 | import ida_name 11 | import ida_bytes 12 | import idautils 13 | 14 | 15 | def enum_segments(): 16 | for segstart in idautils.Segments(): 17 | segend = idc.get_segm_end(segstart) 18 | segname = idc.get_segm_name(segstart) 19 | yield segstart, segend, segname 20 | 21 | 22 | def find_pointers(start, end): 23 | for va in range(start, end-0x8): 24 | ptr = ida_bytes.get_qword(va) 25 | if idc.get_segm_start(ptr) != idc.BADADDR: 26 | yield va, ptr, 8 27 | ptr = ida_bytes.get_dword(va) 28 | if idc.get_segm_start(ptr) != idc.BADADDR: 29 | yield va, ptr, 4 30 | 31 | 32 | def is_head(va): 33 | return ida_bytes.is_head(idc.get_full_flags(va)) 34 | 35 | 36 | def get_head(va): 37 | if is_head(va): 38 | return va 39 | else: 40 | return idc.prev_head(va) 41 | 42 | 43 | def is_code(va): 44 | if is_head(va): 45 | flags = idc.get_full_flags(va) 46 | return ida_bytes.is_code(flags) 47 | else: 48 | head = get_head(va) 49 | return is_code(head) 50 | 51 | 52 | def is_unknown(va): 53 | return ida_bytes.is_unknown(idc.get_full_flags(va)) 54 | 55 | 56 | def main(): 57 | for segstart, segend, segname in enum_segments(): 58 | if segname not in ('.rdata', 'UPX1' ): 59 | continue 60 | 61 | for src, dst, psize in find_pointers(segstart, segend): 62 | if idc.get_segm_name(dst) not in (".rdata", "UPX0"): 63 | continue 64 | 65 | if psize == 8: 66 | size = ida_bytes.get_qword(src + 0x8) 67 | else: 68 | size = ida_bytes.get_dword(src + 0x4) 69 | 70 | if size > 0x100: 71 | continue 72 | if size <= 2: 73 | continue 74 | 75 | buf = ida_bytes.get_bytes(dst, size) 76 | if not buf: 77 | continue 78 | 79 | if b"\x00" in buf: 80 | continue 81 | 82 | try: 83 | s = buf.decode("ascii") 84 | except UnicodeDecodeError: 85 | continue 86 | 87 | print("string pointer: 0x%x -> 0x%x: %s" % (src, dst, s)) 88 | 89 | ida_bytes.del_items(src, 1) 90 | ida_bytes.set_cmt(dst, s, True) 91 | 92 | # pointer 93 | ida_bytes.del_items(src, psize) 94 | ida_bytes.create_data(src, idc.FF_QWORD if size == 8 else idc.FF_DWORD, psize, idc.BADADDR) 95 | # this doesn't seem to always work :-( 96 | idc.op_plain_offset(src, -1, 0) 97 | ida_name.set_name(src, "s_%x" % (src)) 98 | ida_bytes.set_cmt(src, s, True) 99 | 100 | # size 101 | ida_bytes.del_items(src + psize, psize) 102 | ida_bytes.create_data(src + psize, idc.FF_QWORD if size == 8 else idc.FF_DWORD, psize, idc.BADADDR) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /scripts/go/go_fixup_inline_strings.py: -------------------------------------------------------------------------------- 1 | """ 2 | create and annotate references to strings in 64-bit Windows Go executables. 3 | 4 | expect to see the assembly pattern: 5 | 6 | lea reg, $string 7 | mov [stack], reg 8 | mov [stack], $size 9 | """ 10 | import idc 11 | import ida_ua 12 | import ida_name 13 | import ida_bytes 14 | import idautils 15 | 16 | 17 | def enum_segments(): 18 | for segstart in idautils.Segments(): 19 | segend = idc.get_segm_end(segstart) 20 | segname = idc.get_segm_name(segstart) 21 | yield segstart, segend, segname 22 | 23 | 24 | def find_pointers(start, end): 25 | for va in range(start, end-0x8): 26 | ptr = ida_bytes.get_qword(va) 27 | if idc.get_segm_start(ptr) == idc.BADADDR: 28 | continue 29 | 30 | yield va, ptr 31 | 32 | 33 | def is_head(va): 34 | return ida_bytes.is_head(idc.get_full_flags(va)) 35 | 36 | 37 | def get_head(va): 38 | if is_head(va): 39 | return va 40 | else: 41 | return idc.prev_head(va) 42 | 43 | 44 | def is_code(va): 45 | if is_head(va): 46 | flags = idc.get_full_flags(va) 47 | return ida_bytes.is_code(flags) 48 | else: 49 | head = get_head(va) 50 | return is_code(head) 51 | 52 | 53 | def main(): 54 | for segstart, segend, segname in enum_segments(): 55 | for head in idautils.Heads(segstart, segend): 56 | if not is_code(head): 57 | continue 58 | 59 | # pattern: 60 | # 61 | # lea rax, unk_6BDF88 62 | # mov [rsp+0], rax 63 | # mov qword ptr [rsp+8], 40h 64 | if ida_ua.ua_mnem(head) != "lea": 65 | continue 66 | 67 | next_head = ida_bytes.next_head(head, idc.BADADDR) 68 | if ida_ua.ua_mnem(next_head) != "mov": 69 | continue 70 | 71 | next_head2 = ida_bytes.next_head(next_head, idc.BADADDR) 72 | if ida_ua.ua_mnem(next_head2) != "mov": 73 | continue 74 | 75 | dst = idc.get_operand_value(head, 1) 76 | if idc.get_segm_name(dst) not in (".rdata", "UPX1"): 77 | continue 78 | 79 | size = idc.get_operand_value(next_head2, 1) 80 | 81 | if size > 0x100: 82 | continue 83 | if size <= 2: 84 | continue 85 | 86 | buf = ida_bytes.get_bytes(dst, size) 87 | if not buf: 88 | continue 89 | 90 | if b"\x00" in buf: 91 | continue 92 | 93 | try: 94 | s = buf.decode("ascii") 95 | except UnicodeDecodeError: 96 | continue 97 | 98 | print("string pointer: 0x%x -> 0x%x: %s" % (head, dst, s)) 99 | ida_bytes.del_items(dst, 1) 100 | ida_bytes.create_data(dst, idc.FF_BYTE, 1, idc.BADADDR) 101 | ida_bytes.set_cmt(dst, s, True) 102 | ida_name.set_name(dst, "s_%x" % (dst)) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /scripts/goto_file_offset.py: -------------------------------------------------------------------------------- 1 | import ida_loader 2 | import ida_idaapi 3 | import ida_kernwin 4 | 5 | 6 | def main(): 7 | offset = ida_kernwin.ask_addr(0x0, "file offset") 8 | if not offset: 9 | return 10 | 11 | ea = ida_loader.get_fileregion_ea(offset) 12 | if ea == ida_idaapi.BADADDR: 13 | print('error: EA for file offset not found') 14 | return 15 | 16 | print('EA for file offset: 0x%x' % (ea)) 17 | ida_kernwin.jumpto(ea) 18 | 19 | 20 | main() 21 | -------------------------------------------------------------------------------- /scripts/goto_rva.py: -------------------------------------------------------------------------------- 1 | import ida_nalt 2 | import ida_kernwin 3 | 4 | 5 | def main(): 6 | rva = ida_kernwin.ask_addr(0x0, "RVA") 7 | if not rva: 8 | return 9 | 10 | ida_kernwin.jumpto(ida_nalt.get_imagebase() + rva) 11 | 12 | 13 | main() 14 | -------------------------------------------------------------------------------- /scripts/load_file_rwx.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import ida_kernwin 3 | 4 | import idawilli 5 | import idawilli.dbg 6 | 7 | # removeme 8 | import ida_idaapi 9 | ida_idaapi.require('idawilli') 10 | ida_idaapi.require('idawilli.dbg') 11 | 12 | 13 | def main(): 14 | path = ida_kernwin.ask_file(False, "*", "file to load") 15 | if not path: 16 | return 17 | 18 | with open(path, "rb") as f: 19 | buf = tuple(f.read()) 20 | 21 | if len(buf) == 0: 22 | print("empty file, cancelling") 23 | return 24 | 25 | size = idawilli.align(len(buf), 0x1000) 26 | print("size: 0x%x" % (len(buf))) 27 | print("aligned size: 0x%x" % (size)) 28 | 29 | addr = idawilli.dbg.allocate_rwx(size) 30 | print("allocated 0x%x bytes at 0x%x" % (size, addr)) 31 | 32 | idawilli.dbg.patch_bytes(addr, buf) 33 | print("patched file to 0x%x" % (addr)) 34 | 35 | print("ok") 36 | 37 | main() -------------------------------------------------------------------------------- /scripts/load_map_file.py: -------------------------------------------------------------------------------- 1 | # This script prompts for the path to a file 2 | # which contains a two column, whitespace-delimited list 3 | # 4 | # addr function 5 | # 00bca02c ADVAPI32!InitializeSecurityDescriptor 6 | import ida_kernwin 7 | import ida_idaapi 8 | import ida_bytes 9 | import ida_name 10 | 11 | 12 | def get_bitness(): 13 | info = ida_idaapi.get_inf_structure() 14 | 15 | if info.is_64bit(): 16 | return 64 17 | elif info.is_32bit(): 18 | return 32 19 | else: 20 | return 16 21 | 22 | 23 | def make_pointer(ea): 24 | if get_bitness() == 16: 25 | ida_bytes.create_word(ea, 2) 26 | 27 | elif get_bitness() == 32: 28 | ida_bytes.create_dword(ea, 4) 29 | 30 | elif get_bitness() == 64: 31 | ida_bytes.create_qword(ea, 8) 32 | 33 | else: 34 | raise RuntimeError("unexpected bitness") 35 | 36 | # -------------------------------------------------------------------------- 37 | class MyForm(ida_kernwin.Form): 38 | def __init__(self): 39 | ida_kernwin.Form.__init__(self, r"""select map file 40 | <#Select an annotation file to open#Browse to open:{iFileOpen}> 41 | """, 42 | { 'iFileOpen': ida_kernwin.Form.FileInput(open=True)}) 43 | 44 | def OnFormChange(self, fid): 45 | return 1 46 | 47 | def prompt_file(): 48 | f = ida_kernwin.Form( 49 | r"""select map file 50 | <#Select an annotation file to open#Browse to open:{iFileOpen}> 51 | """, 52 | { 'iFileOpen': ida_kernwin.Form.FileInput(open=True)}) 53 | f.Compile() 54 | f.iFileOpen.value = "" 55 | ok = f.Execute() 56 | assert(ok == 1) 57 | path = f.iFileOpen.value 58 | f.Free() 59 | return path 60 | 61 | 62 | with open(prompt_file(), "rb") as f: 63 | for line in f.read().decode("utf-8").split("\n"): 64 | line = line.replace("`", "") 65 | 66 | # split by any whitespace 67 | parts = line.split() 68 | 69 | if len(parts) != 2: 70 | print("skipping line: " + line) 71 | continue 72 | 73 | try: 74 | address = int(parts[0], 0x10) 75 | except: 76 | print("skipping line: " + line) 77 | continue 78 | 79 | function = parts[1].strip() 80 | if "!" in function: 81 | dll, _, name = function.partition("!") 82 | else: 83 | name = function 84 | 85 | print("%s %s" % (hex(address), name)) 86 | 87 | make_pointer(address) 88 | ida_name.set_name(address, name) 89 | 90 | print("Done.") -------------------------------------------------------------------------------- /scripts/parse_uuid.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import ida_bytes 4 | import ida_kernwin 5 | 6 | 7 | buf = ida_bytes.get_bytes(ida_kernwin.get_screen_ea(), 0x10) 8 | uid = uuid.UUID(bytes_le=buf) 9 | print(uid) 10 | -------------------------------------------------------------------------------- /scripts/resolve_ptrs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | scan all pointers in the segment that contains EA, 3 | checking if they point to an external name. 4 | if so, rename the pointer. 5 | 6 | this is intended to be used during debugging of a packed sample. 7 | ''' 8 | import idc 9 | import ida_name 10 | import ida_bytes 11 | import ida_segment 12 | 13 | 14 | # TODO: arch 15 | psize = 4 16 | 17 | def get_ptr(ea): 18 | # TODO: arch 19 | return ida_bytes.get_dword(ea) 20 | 21 | 22 | def make_ptr(ea): 23 | # TODO: arch 24 | ida_bytes.del_items(ea, 0, psize) 25 | return ida_bytes.create_dword(ea, psize) 26 | 27 | 28 | def enum_ptrs(start, end): 29 | for ea in range(start, end, psize): 30 | yield (ea, get_ptr(ea)) 31 | 32 | 33 | def enum_segment_ptrs(ea): 34 | seg = ida_segment.getseg(ea) 35 | for (ea, ptr) in enum_ptrs(seg.start_ea, seg.end_ea): 36 | yield (ea, ptr) 37 | 38 | 39 | 40 | for ea, ptr in enum_segment_ptrs(idc.get_screen_ea()): 41 | name = ida_name.get_name(ptr) 42 | 43 | if not name: 44 | continue 45 | 46 | if name.startswith('loc_'): 47 | continue 48 | 49 | print(hex(ea) + ': ' + name) 50 | 51 | make_ptr(ea) 52 | ida_name.set_name(ea, name) 53 | -------------------------------------------------------------------------------- /scripts/save_segment.py: -------------------------------------------------------------------------------- 1 | ''' 2 | IDAPython script that saves the content of a segment to a file. 3 | Prompts the user for: 4 | - segment name 5 | - file path 6 | 7 | Useful for extracting data from memory dumps. 8 | 9 | Author: Willi Ballenthin 10 | Licence: Apache 2.0 11 | ''' 12 | import logging 13 | from collections import namedtuple 14 | 15 | import idaapi 16 | import ida_bytes 17 | import ida_segment 18 | 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | class BadInputError(Exception): 24 | pass 25 | 26 | 27 | Segment = namedtuple('SegmentBuffer', ['path', 'name']) 28 | 29 | 30 | def prompt_for_segment(): 31 | ''' :returns: a Segment instance, or raises BadInputError ''' 32 | class MyForm(idaapi.Form): 33 | def __init__(self): 34 | idaapi.Form.__init__(self, """STARTITEM 0 35 | add segment by buffer 36 | 37 | <##segment name:{name}> 38 | <##output path:{path}> 39 | """, 40 | { 41 | 'path': idaapi.Form.FileInput(save=True), 42 | 'name': idaapi.Form.StringInput(), 43 | }) 44 | 45 | def OnFormChange(self, fid): 46 | return 1 47 | 48 | f = MyForm() 49 | f.Compile() 50 | f.path.value = "" 51 | f.name.value = "" 52 | ok = f.Execute() 53 | if ok != 1: 54 | raise BadInputError('user cancelled') 55 | 56 | path = f.path.value 57 | if path == "" or path is None: 58 | raise BadInputError('bad path provided') 59 | 60 | name = f.name.value 61 | if name == "" or name is None: 62 | raise BadInputError('bad name provided') 63 | 64 | f.Free() 65 | return Segment(path, name) 66 | 67 | 68 | def main(argv=None): 69 | if argv is None: 70 | argv = sys.argv[:] 71 | 72 | try: 73 | seg_spec = prompt_for_segment() 74 | except BadInputError: 75 | logger.error('bad input, exiting...') 76 | return -1 77 | 78 | seg = ida_segment.get_segm_by_name(seg_spec.name) 79 | if not seg: 80 | logger.error("bad segment, exiting...") 81 | 82 | buf = ida_bytes.get_bytes(seg.start_ea, seg.end_ea - seg.start_ea) 83 | with open(seg_spec.path, "wb") as f: 84 | f.write(buf) 85 | 86 | logger.info("wrote %x bytes", len(buf)) 87 | 88 | 89 | if __name__ == '__main__': 90 | logging.basicConfig(level=logging.DEBUG) 91 | main() 92 | -------------------------------------------------------------------------------- /scripts/windb_symbols/apply_windbg_symbols.py: -------------------------------------------------------------------------------- 1 | from idaapi import * 2 | from idc import * 3 | 4 | # This script prompts for the path to a file 5 | # which contains a three column, whitespace-delimited list 6 | # 7 | # addr deref'd function 8 | # 00bca02c 77dd79c6 ADVAPI32!InitializeSecurityDescriptor 9 | 10 | 11 | def SetName(ea, s): 12 | idaapi.set_name(ea, s) 13 | 14 | 15 | def is_32(): 16 | try: 17 | _ = __EA64__ 18 | return False 19 | except: 20 | return True 21 | 22 | 23 | def make_pointer(ea): 24 | if is_32(): 25 | MakeUnkn(ea, 4) 26 | MakeDword(ea) 27 | else: 28 | MakeUnkn(ea, 8) 29 | MakeQword(ea) 30 | 31 | # -------------------------------------------------------------------------- 32 | class MyForm(Form): 33 | def __init__(self): 34 | self.invert = False 35 | Form.__init__(self, r"""STARTITEM 36 | <#Select an annotation file to open#Browse to open:{iFileOpen}> 37 | """, { 'iFileOpen': Form.FileInput(open=True), }) 38 | 39 | def OnFormChange(self, fid): 40 | return 1 41 | 42 | try: 43 | f = MyForm() 44 | f.Compile() 45 | f.iFileOpen.value = "" 46 | ok = f.Execute() 47 | if ok == 1: 48 | print f.iFileOpen.value 49 | with open(f.iFileOpen.value, "rb") as g: 50 | for line in g.read().split("\n"): 51 | line = line.replace("`", "") 52 | parts = line.split(" ") 53 | if len(parts) != 4: 54 | continue 55 | try: 56 | address = int(parts[0], 0x10) 57 | except: 58 | continue 59 | function = parts[3].strip() 60 | dll, _, name = function.partition("!") 61 | print "%s %s" % (hex(address), name) 62 | 63 | make_pointer(address) 64 | SetName(address, name) 65 | f.Free() 66 | except Exception as e: 67 | print "Unexpected error: ", e 68 | print "Done." 69 | -------------------------------------------------------------------------------- /scripts/write_file.py: -------------------------------------------------------------------------------- 1 | import idc 2 | import ida_kernwin 3 | 4 | import idawilli 5 | import idawilli.dbg 6 | 7 | # removeme 8 | import ida_idaapi 9 | ida_idaapi.require('idawilli') 10 | ida_idaapi.require('idawilli.dbg') 11 | 12 | 13 | def main(): 14 | path = ida_kernwin.ask_file(False, "*", "file to load") 15 | if not path: 16 | return 17 | 18 | with open(path, "rb") as f: 19 | buf = tuple(f.read()) 20 | 21 | if len(buf) == 0: 22 | print("empty file, cancelling") 23 | return 24 | 25 | size = idawilli.align(len(buf), 0x1000) 26 | print("size: 0x%x" % (len(buf))) 27 | print("aligned size: 0x%x" % (size)) 28 | 29 | addr = ida_kernwin.ask_addr(idc.get_screen_ea(), "location to write") 30 | if not addr: 31 | return 32 | 33 | idawilli.dbg.patch_bytes(addr, buf) 34 | 35 | print("ok") 36 | 37 | main() -------------------------------------------------------------------------------- /scripts/yara_fn/readme.md: -------------------------------------------------------------------------------- 1 | # yara_fn 2 | 3 | IDAPython script that generates a YARA rule to match against the 4 | basic blocks of the current function. It masks out relocation bytes 5 | and ignores jump instructions (given that we're already trying to 6 | match compiler-specific bytes, this is of arguable benefit). 7 | 8 | If python-yara is installed, the IDAPython script also validates that 9 | the generated rule matches at least one segment in the current file. 10 | 11 | ## installation 12 | 13 | none 14 | 15 | ## usage 16 | 17 | invoke the script via `File->Script file...`. review the text written to the output pane. 18 | -------------------------------------------------------------------------------- /scripts/yara_fn/yara_fn.py: -------------------------------------------------------------------------------- 1 | ''' 2 | IDAPython script that generates a YARA rule to match against the 3 | basic blocks of the current function. It masks out relocation bytes 4 | and ignores jump instructions (given that we're already trying to 5 | match compiler-specific bytes, this is of arguable benefit). 6 | 7 | If python-yara is installed, the IDAPython script also validates that 8 | the generated rule matches at least one segment in the current file. 9 | 10 | author: Willi Ballenthin 11 | ''' 12 | 13 | import logging 14 | from collections import namedtuple 15 | 16 | import idc 17 | import idaapi 18 | import idautils 19 | 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | BasicBlock = namedtuple('BasicBlock', ['va', 'size']) 24 | 25 | 26 | # each rule must have at least this many non-masked bytes 27 | MIN_BB_BYTE_COUNT = 4 28 | 29 | 30 | def get_basic_blocks(fva): 31 | ''' 32 | return sequence of `BasicBlock` instances for given function. 33 | ''' 34 | ret = [] 35 | func = idaapi.get_func(fva) 36 | if func is None: 37 | return ret 38 | 39 | for bb in idaapi.FlowChart(func): 40 | ret.append(BasicBlock(va=bb.start_ea, 41 | size=bb.end_ea - bb.start_ea)) 42 | 43 | return ret 44 | 45 | 46 | def get_function(va): 47 | ''' 48 | return va for first instruction in function that contains given va. 49 | ''' 50 | return idaapi.get_func(va).start_ea 51 | 52 | 53 | Rule = namedtuple('Rule', ['name', 'bytes', 'masked_bytes']) 54 | 55 | 56 | def is_jump(va): 57 | ''' 58 | return True if the instruction at the given address appears to be a jump. 59 | ''' 60 | return idc.print_insn_mnem(va).startswith('j') 61 | 62 | 63 | def get_basic_block_rule(bb): 64 | ''' 65 | create and format a YARA rule for a single basic block. 66 | mask relocation bytes into unknown bytes (like '??'). 67 | do not include final instructions if they are jumps. 68 | ''' 69 | # fetch the instruction start addresses 70 | insns = [] 71 | va = bb.va 72 | while va < bb.va + bb.size: 73 | insns.append(va) 74 | va = idc.next_head(va) 75 | 76 | # drop the last instruction if its a jump 77 | if is_jump(insns[-1]): 78 | insns = insns[:-1] 79 | 80 | bytes = [] 81 | # `masked_bytes` is the list of formatted bytes, 82 | # not yet join'd for performance. 83 | masked_bytes = [] 84 | for va in insns: 85 | size = idc.get_item_size(va) 86 | if idaapi.contains_fixups(va, size): 87 | # fetch the fixup locations within this one instruction. 88 | fixups = [] 89 | fixupva = idaapi.get_next_fixup_ea(va) 90 | fixups.append(fixupva) 91 | # TODO: assume the fixup size is four bytes, probably bad. 92 | fixupva += 4 93 | 94 | while fixupva < va + size: 95 | fixupva = idaapi.get_next_fixup_ea(fixupva) 96 | fixups.append(fixupva) 97 | # TODO: assume the fixup size is four bytes, probably bad. 98 | fixupva += 4 99 | 100 | # assume each fixup is four bytes (TODO!), 101 | # and compute the addresses of each component byte. 102 | fixup_byte_addrs = set([]) 103 | for fixup in fixups: 104 | for i in range(fixup, fixup+4): 105 | fixup_byte_addrs.add(i) 106 | 107 | # fetch and format each byte of the instruction, 108 | # possibly masking it into an unknown byte if its a fixup. 109 | for i, byte in enumerate(idc.get_bytes(va, size)): 110 | byte_addr = i + va 111 | if byte_addr in fixup_byte_addrs: 112 | bytes.append(byte) 113 | masked_bytes.append('??') 114 | else: 115 | bytes.append(byte) 116 | masked_bytes.append('%02X' % (byte)) 117 | elif 'call' in idc.print_insn_mnem(va): 118 | for i, byte in enumerate(idc.get_bytes(va, size)): 119 | bytes.append(byte) 120 | masked_bytes.append('??') 121 | else: 122 | for byte in idc.get_bytes(va, size): 123 | bytes.append(byte) 124 | masked_bytes.append('%02X' % (byte)) 125 | 126 | return Rule('$0x%x' % (bb.va), bytes, masked_bytes) 127 | 128 | 129 | def format_rules(fva, rules): 130 | ''' 131 | given the address of a function, and the byte signatures for basic blocks in 132 | the function, format a complete YARA rule that matches all of the 133 | basic block signatures. 134 | ''' 135 | name = idc.get_func_name(fva) 136 | 137 | # some characters aren't valid for YARA rule names 138 | safe_name = name 139 | BAD_CHARS = '@ /\\!@#$%^&*()[]{};:\'",./<>?' 140 | for c in BAD_CHARS: 141 | safe_name = safe_name.replace(c, '') 142 | 143 | md5 = idautils.GetInputFileMD5().hex() 144 | ret = [] 145 | ret.append(f'rule a_{md5}_{safe_name}') 146 | ret.append('{') 147 | ret.append(' meta:') 148 | ret.append(f' sample_md5 = "{md5}"') 149 | ret.append(f' function_address = "0x{fva}"') 150 | ret.append(f' function_name = "{name}"') 151 | ret.append(' strings:') 152 | for rule in rules: 153 | formatted_rule = ' '.join(rule.masked_bytes) 154 | ret.append(f' {rule.name} = {{{formatted_rule}}}') 155 | ret.append(' condition:') 156 | ret.append(' all of them') 157 | ret.append('}') 158 | return '\n'.join(ret) 159 | 160 | 161 | def create_yara_rule_for_function(fva): 162 | ''' 163 | given the address of a function, generate and format a complete YARA rule 164 | that matches the basic blocks. 165 | ''' 166 | rules = [] 167 | for bb in get_basic_blocks(fva): 168 | rule = get_basic_block_rule(bb) 169 | 170 | # ensure there at least MIN_BB_BYTE_COUNT 171 | # non-masked bytes in the rule, or ignore it. 172 | # this will reduce the incidence of many very small matches. 173 | unmasked_count = len([b for b in rule.masked_bytes if b != '??']) 174 | if unmasked_count < MIN_BB_BYTE_COUNT: 175 | continue 176 | 177 | rules.append(rule) 178 | 179 | return format_rules(fva, rules) 180 | 181 | 182 | def get_segment_buffer(segstart): 183 | ''' 184 | fetch the bytes of the section that starts at the given address. 185 | if the entire section cannot be accessed, try smaller regions until it works. 186 | ''' 187 | segend = idaapi.getseg(segstart).end_ea 188 | buf = None 189 | segsize = segend - segstart 190 | while buf is None: 191 | buf = idc.get_bytes(segstart, segsize) 192 | if buf is None: 193 | segsize -= 0x1000 194 | return buf 195 | 196 | 197 | Segment = namedtuple('Segment', ['start', 'size', 'name', 'buf']) 198 | 199 | 200 | def get_segments(): 201 | ''' 202 | fetch the segments in the current executable. 203 | ''' 204 | for segstart in idautils.Segments(): 205 | segend = idaapi.getseg(segstart).end_ea 206 | segsize = segend - segstart 207 | segname = str(idc.SegName(segstart)).rstrip('\x00') 208 | segbuf = get_segment_buffer(segstart) 209 | yield Segment(segstart, segend, segname, segbuf) 210 | 211 | 212 | class TestDidntRunError(Exception): 213 | pass 214 | 215 | 216 | def test_yara_rule(rule): 217 | ''' 218 | try to match the given rule against each segment in the current exectuable. 219 | raise TestDidntRunError if its not possible to import the YARA library. 220 | return True if there's at least one match, False otherwise. 221 | ''' 222 | try: 223 | import yara 224 | except ImportError: 225 | logger.warning("can't test rule: failed to import python-yara") 226 | raise TestDidntRunError('python-yara not available') 227 | 228 | r = yara.compile(source=rule) 229 | 230 | for segment in get_segments(): 231 | matches = r.match(data=segment.buf) 232 | if len(matches) > 0: 233 | logger.info('generated rule matches section: {segment.name}') 234 | return True 235 | return False 236 | 237 | 238 | def main(): 239 | va = idc.get_screen_ea() 240 | fva = get_function(va) 241 | print(('-' * 80)) 242 | rule = create_yara_rule_for_function(fva) 243 | print(rule) 244 | 245 | ''' 246 | if test_yara_rule(rule): 247 | print('success: validated the generated rule') 248 | else: 249 | print('error: failed to validate generated rule') 250 | ''' 251 | 252 | 253 | if __name__ == '__main__': 254 | logging.basicConfig(level=logging.DEBUG) 255 | logging.getLogger().setLevel(logging.DEBUG) 256 | main() 257 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import setuptools 5 | 6 | setup(name='idawilli', 7 | version='0.1', 8 | description='IDA Pro resources, scripts, and configurations.', 9 | author='Willi Ballenthin', 10 | author_email='william.ballenthin@fireeye.com', 11 | license='Apache License (2.0)', 12 | packages=setuptools.find_packages(), 13 | classifiers = ["Programming Language :: Python", 14 | "Programming Language :: Python :: 2", 15 | "Operating System :: OS Independent", 16 | "License :: OSI Approved :: Apache Software License"], 17 | install_requires=[ 18 | 'pytest', 19 | ], 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /tests/readme.md: -------------------------------------------------------------------------------- 1 | # tests 2 | 3 | here is the code that demonstrates that the `idawilli` module works as advertised. 4 | to run, execute the `test_all.py` IDAPython script within IDA Pro. 5 | -------------------------------------------------------------------------------- /tests/test_all.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | import idc 5 | import idaapi 6 | import pytest 7 | 8 | import idawilli 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def main(): 15 | logging.basicConfig(level=logging.DEBUG) 16 | logging.getLogger().setLevel(logging.DEBUG) 17 | 18 | pytest.main(['--capture=sys', os.path.dirname(__file__)]) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /themes/colors/adwaita-dark/adwaita-dark.clr: -------------------------------------------------------------------------------- 1 | [DISASM] 2 | 000000 // 3 | ff0000 //Default color 4 | a46534 //Regular comment 5 | 808080 //Repeatable comment 6 | 808080 //Automatic comment 7 | 36342e //Instruction 8 | 800000 //Dummy Data Name 9 | a46534 //Regular Data Name 10 | a46534 //Demangled Name 11 | 800000 //Punctuation 12 | 069a4e //Char constant in instruction 13 | 00ff00 //String constant in instruction 14 | 069a4e //Numeric constant in instruction 15 | 2929ef //Void operand 16 | 069a4e //Code reference 17 | ff8080 //Data reference 18 | 0000ff //Code reference to tail byte 19 | 008080 //Data reference to tail byte 20 | 010101 //Error or problem 21 | c0c0c0 //Line prefix 22 | a46534 //Binary line prefix bytes 23 | a46534 //Extra line 24 | ff0000 //Alternative operand 25 | 808080 //Hidden name 26 | ff8080 //Library function name 27 | 008000 //Local variable name 28 | 800000 //Dummy code name 29 | a46534 //Assembler directive 30 | 800080 //Macro 31 | 069a4e //String constant in data directive 32 | 008000 //Char constant in data directive 33 | 069a4e //Numeric constant in data directive 34 | 800000 //Keywords 35 | 800000 //Register name 36 | 2929ef //Imported name 37 | 008080 //Segment name 38 | 800000 //Dummy unknown name 39 | cf9f72 //Regular code name 40 | 800000 //Regular unknown name 41 | a46534 //Collapsed line 42 | 000000 //Max color number 43 | cfd7d3 //Line prefix: library function 44 | eceeee //Line prefix: regular function 45 | ffff00 //Line prefix: instruction 46 | 000000 //Line prefix: data 47 | 000080 //Line prefix: unexplored 48 | 808080 //Line prefix: externs 49 | 008080 //Line prefix: current item 50 | 2929ef //Line prefix: current line 51 | 000000 //Punctuation 52 | ff0000 //Opcode bytes 53 | 000000 //Manual operand 54 | [NAVBAR] 55 | ffffaa //Library function 56 | e8a200 //Regular function 57 | 577ab9 //Instruction 58 | c0c0c0 //Data item 59 | 6bb6b6 //Unexplored 60 | ffa6ff //External symbol 61 | 5b5bff //Errors 62 | 000000 //Gaps 63 | 7fffff //Cursor 64 | 00aaff //Address 65 | [DEBUG] 66 | ffd060 //Current IP 67 | ffa0a0 //Current IP (Enabled) 68 | 408020 //Current IP (Disabled) 69 | ffffcc //Current IP (Unavailible) 70 | 0000ff //Address 71 | 00ff00 //Address (Enabled) 72 | 004080 //Address (Disabled) 73 | 0080ff //Address (Unavailible) 74 | 000000 //Registers 75 | ff0000 //Registers (Changed) 76 | 800080 //Registers (Edited) 77 | [ARROW] 78 | c0c0c0 //Jump in current function 79 | 0000ff //Jump external to function 80 | 000000 //Jump under the cursor 81 | 008000 //Jump target 82 | ff4040 //Register target 83 | [GRAPH] 84 | 292723 //Top color 85 | 292723 //Bottom color 86 | 535755 //Normal title 87 | 9c5d21 //Selected title 88 | 9c5d21 //Current title 89 | 00ffff //Group frame 90 | 000000 //Node shadow 91 | ffffcc //Highlight color 1 92 | ccffcc //Highlight color 2 93 | 0000ff //Foreign node 94 | ff0000 //Normal edge 95 | 008000 //Yes edge 96 | 0000ff //No edge 97 | ff00ff //Highlighted edge 98 | ffff00 //Current edge 99 | [MISC] 100 | eceeee //Message text 101 | ffffff //Message background 102 | 404080 //Patched bytes 103 | 0080ff //Unsaved changes 104 | [OTHER] 105 | 4fe9fc //Highlight color 106 | eceeee //Hint color 107 | [SYNTAX] 108 | cf9f72 1 0 //Keyword 1 109 | cf9f72 1 0 //Keyword 2 110 | 0000cc 1 0 //Keyword 3 111 | 2929ef 0 0 //String 112 | 069a4e 1 1 //Comment 113 | cf9f72 1 0 //Preprocessor 114 | 8b8b00 1 0 //Number 115 | -------------------------------------------------------------------------------- /themes/colors/adwaita-dark/readme.md: -------------------------------------------------------------------------------- 1 | # adwaita-dark 2 | 3 | a color theme based on gnome3's adwaita-dark variant. 4 | use with the corresponding skin. 5 | 6 | 7 | ## screenshot 8 | 9 | ![screenshot](screenshot.png?raw=true "adwaita-dark") 10 | 11 | 12 | ## colors 13 | 14 | ``` 15 | background #33393b 16 | background (med) #919494 17 | background (light) #eeeeec 18 | dark #2E3436 19 | dark (light) #555753 20 | red #CC0000 21 | red (light) #EF2929 22 | green #4E9A06 23 | green (light) #8AE234 24 | yellow #C4A000 25 | yellow (light) #FCE94F 26 | blue #3465A4 27 | blue (light) #729FCF 28 | purple #75507B 29 | purple (light) #AD7FA8 30 | cyan #06989A 31 | cyan (light) #34E2E2 32 | light #D3D7CF 33 | light (light) #EEEEEC 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /themes/colors/adwaita-dark/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/colors/adwaita-dark/screenshot.png -------------------------------------------------------------------------------- /themes/colors/readme.md: -------------------------------------------------------------------------------- 1 | # colors 2 | 3 | these are color themes. 4 | install them using `options->colors->import`. 5 | -------------------------------------------------------------------------------- /themes/colors/willi/theme.css: -------------------------------------------------------------------------------- 1 | 2 | AskText QLabel#counterlabel 3 | { 4 | color: grey; 5 | } 6 | 7 | AskText QLabel#counterlabel[invalid=true] 8 | { 9 | color: red; 10 | } 11 | 12 | /* make that guy behave as if it were a regular widget, by killing the default QGroupBox spacing */ 13 | LabeledWidgetContainer 14 | { 15 | border: 0; 16 | padding: 0; 17 | } 18 | 19 | QSplitter::handle:horizontal 20 | { 21 | width: 4px; 22 | } 23 | 24 | QSplitter::handle:vertical 25 | { 26 | height: 4px; 27 | } 28 | 29 | DockWidgetTitleButton:!hover 30 | { 31 | border: none; 32 | } 33 | 34 | ActionsInspector QLineEdit 35 | { 36 | width: 16em; 37 | } 38 | 39 | ActionsInspector QKeySequenceEdit 40 | { 41 | width: 16em; 42 | } 43 | 44 | CustomIDAMemo 45 | { 46 | /* misc */ 47 | qproperty-line-fg-patched-bytes : #804040; /* patched bytes (brown-ish) */ 48 | qproperty-line-fg-unsaved-changes : #FF8000; /* unsaved changes (orange-ish) */ 49 | qproperty-line-bg-highlight : #FFFF00; /* highlighting background */ 50 | qproperty-line-pfx-current-item : rgba(0, 0, 0, 0);/* Line prefix: Current item (transparent by default) */ 51 | qproperty-line-bgovl-current-line : rgba(80, 80, 80, 0.15); /* current line background overlay */ 52 | qproperty-line-bgovl-trace : rgba(255, 255, 0, 0.20); /* Trace line background overlay */ 53 | qproperty-line-bgovl-trace-ovl : rgba(255, 188, 180, 0.40); /* Second trace line background overlay */ 54 | qproperty-line-bgovl-extra-1 : rgba(80, 255, 80, 0.25); /* Extra background overlay #1 */ 55 | qproperty-line-bgovl-extra-2 : rgba(238, 255, 136, 0.25); /* Extra background overlay #2 */ 56 | qproperty-line-bgovl-extra-3 : rgba(255, 170, 0, 0.4); /* Extra background overlay #3 */ 57 | 58 | /* graph */ 59 | qproperty-graph-bg-top : white; 60 | qproperty-graph-bg-bottom : #E0F8FF; 61 | qproperty-graph-node-title-normal : white; 62 | qproperty-graph-node-title-selected : #B1F9F9; 63 | qproperty-graph-node-title-current : #A0CFCF; 64 | qproperty-graph-node-frame-group : yellow; 65 | qproperty-graph-node-shadow : black; 66 | qproperty-graph-node-high1 : #CCFFFF; 67 | qproperty-graph-node-high2 : #CCFFCC; 68 | qproperty-graph-node-foreign : red; 69 | qproperty-graph-edge-normal : blue; 70 | qproperty-graph-edge-yes : green; 71 | qproperty-graph-edge-no : red; 72 | qproperty-graph-edge-high : fuchsia; 73 | qproperty-graph-edge-current : cyan; 74 | 75 | /* bpts */ 76 | qproperty-line-bg-bpt-enabled : red; 77 | qproperty-line-bg-bpt-disabled : lime; 78 | qproperty-line-bg-bpt-unavailable : #FF8000; 79 | } 80 | 81 | CustomIDAMemo[debugging="true"] 82 | { 83 | qproperty-line-bg-default : #CCFFFF; 84 | qproperty-line-bgovl-current-ip : rgba(0, 195, 255, .45); 85 | } 86 | 87 | CustomIDAMemo[hints="true"] 88 | { 89 | qproperty-line-bg-default : #FFFFE1; /* hints background (pale yellow) */ 90 | } 91 | 92 | TextArrows 93 | { 94 | qproperty-jump-in-function : silver; 95 | qproperty-jump-external-to-function : red; 96 | qproperty-jump-under-cursor : black; 97 | qproperty-jump-target : green; 98 | qproperty-register-target : #4040FF; 99 | qproperty-bpt-possible : #60D0FF; 100 | } 101 | 102 | MainMsgList 103 | { 104 | color : black; 105 | background-color : white; 106 | } 107 | 108 | TCpuRegs 109 | { 110 | background-color: #CCFFFF; 111 | qproperty-register-defined: black; 112 | qproperty-register-changed: blue; 113 | qproperty-register-edited: purple; 114 | qproperty-register-unavailable: gray; 115 | } 116 | 117 | TCpuRegs QPushButton 118 | { 119 | background: transparent; 120 | } 121 | 122 | TCpuRegs IDALabel 123 | { 124 | color: blue; 125 | } 126 | 127 | navband_t 128 | { 129 | qproperty-lib-function : #AAFFFF; 130 | qproperty-function : #00A2E8; 131 | qproperty-code : #B97A57; 132 | qproperty-data : silver; 133 | qproperty-undefined : #B6B66B; 134 | qproperty-extern : #FFA6FF; 135 | qproperty-error : #FF5B5B; 136 | qproperty-gap : black; 137 | qproperty-cursor : #FFFF7F; 138 | qproperty-auto-analysis-cursor : #FFAA00; 139 | qproperty-lumina-function : #32CD32; 140 | } 141 | 142 | TextEdit 143 | { 144 | qproperty-keyword1-fg: blue; 145 | qproperty-keyword1-weight: 0; 146 | qproperty-keyword1-italic: 0; 147 | 148 | qproperty-keyword2-fg: purple; 149 | qproperty-keyword2-weight: 0; 150 | qproperty-keyword2-italic: 0; 151 | 152 | qproperty-keyword3-fg: red; 153 | qproperty-keyword3-weight: 0; 154 | qproperty-keyword3-italic: 0; 155 | 156 | qproperty-string-fg: darkred; 157 | qproperty-string-weight: 0; 158 | qproperty-string-italic: 0; 159 | 160 | qproperty-comment-fg: darkgreen; 161 | qproperty-comment-weight: 0; 162 | qproperty-comment-italic: 1; 163 | 164 | qproperty-preprocessor-fg: blue; 165 | qproperty-preprocessor-weight: 1; 166 | qproperty-preprocessor-italic: 0; 167 | 168 | qproperty-number-fg: darkcyan; 169 | qproperty-number-weight: 1; 170 | qproperty-number-italic: 0; 171 | } 172 | 173 | TextEdit text_edit_margin_widget_t 174 | { 175 | color: grey; 176 | qproperty-header-color: dimgrey; 177 | } 178 | 179 | TChooser 180 | { 181 | qproperty-highlight-bg-default: yellow; 182 | qproperty-highlight-bg-selected: #00C0C0; 183 | } 184 | 185 | 186 | CustomIDAMemo 187 | { 188 | qproperty-line-fg-default : blue; /* Default */ 189 | qproperty-line-fg-regular-comment : blue; /* Regular comment */ 190 | qproperty-line-fg-repeatable-comment : gray; /* Repeatable commen */ 191 | qproperty-line-fg-automatic-comment : gray; /* Automatic comment */ 192 | qproperty-line-fg-insn : navy; /* Instruction */ 193 | qproperty-line-fg-dummy-data-name : navy; /* Dummy Data Name */ 194 | qproperty-line-fg-regular-data-name : blue; /* Regular Data Name */ 195 | qproperty-line-fg-demangled-name : blue; /* Demangled Name */ 196 | qproperty-line-fg-punctuation : navy; /* Punctuation */ 197 | qproperty-line-fg-charlit-in-insn : green; /* Char constant */ 198 | qproperty-line-fg-strlit-in-insn : lime; /* String constant */ 199 | qproperty-line-fg-numlit-in-insn : green; /* Numeric constant */ 200 | qproperty-line-fg-void-opnd : #FF8000; /* Void operand */ 201 | qproperty-line-fg-code-xref : green; /* Code reference */ 202 | qproperty-line-fg-data-xref : #8080FF; /* Data reference */ 203 | qproperty-line-fg-code-xref-to-tail : red; /* Code reference to tail byte */ 204 | qproperty-line-fg-data-xref-to-tail : olive; /* Data reference to tail byte */ 205 | qproperty-line-fg-error : #010101; /* Error or problem */ 206 | qproperty-line-fg-line-prefix : silver; /* Line prefix */ 207 | qproperty-line-fg-opcode-byte : blue; /* Opcode bytes */ 208 | qproperty-line-fg-extra-line : blue; /* Extra line */ 209 | qproperty-line-fg-alt-opnd : blue; /* Alternative operand */ 210 | qproperty-line-fg-hidden : gray; /* Hidden name */ 211 | qproperty-line-fg-libfunc : #8080FF; /* Library function name */ 212 | qproperty-line-fg-locvar : green; /* Local variable name */ 213 | qproperty-line-fg-dummy-code-name : navy; /* Dummy code name */ 214 | qproperty-line-fg-asm-directive : blue; /* Assembler directive */ 215 | qproperty-line-fg-macro : purple; /* Macro */ 216 | qproperty-line-fg-strlit-in-data : green; /* String constant in data directive */ 217 | qproperty-line-fg-charlit-in-data : green; /* Char constant in data directive */ 218 | qproperty-line-fg-numlit-in-data : #008040; /* Numeric constant in data directive */ 219 | qproperty-line-fg-keyword : navy; /* Keywords (offset, byte ptr, near) */ 220 | qproperty-line-fg-register-name : navy; /* Register name */ 221 | qproperty-line-fg-import-name : fuchsia; /* Imported name */ 222 | qproperty-line-fg-segment-name : olive; /* Segment name */ 223 | qproperty-line-fg-dummy-unknown-name : navy; /* Dummy unknown name */ 224 | qproperty-line-fg-code-name : blue; /* Regular Code Name */ 225 | qproperty-line-fg-unknown-name : navy; /* Regular Unknown Name */ 226 | qproperty-line-fg-collapsed-line : blue; /* Collapsed line */ 227 | qproperty-line-bg-default : white; /* Default background */ 228 | qproperty-line-bg-selected : #C0BBAF; /* Selected background */ 229 | qproperty-line-pfx-libfunc : cyan; /* Line prefix: Library function */ 230 | qproperty-line-pfx-func : black; /* Line prefix: Regular function */ 231 | qproperty-line-pfx-insn : maroon; /* Line prefix: Single instruction */ 232 | qproperty-line-pfx-data : gray; /* Line prefix: Data bytes */ 233 | qproperty-line-pfx-unexplored : olive; /* Line prefix: Unexplored byte */ 234 | qproperty-line-pfx-extern : fuchsia; /* Line prefix: External name definition segment */ 235 | qproperty-line-pfx-current-line : blue; /* Line prefix: Current line */ 236 | qproperty-line-pfx-hidden-line : black; /* Line prefix: Hidden line */ 237 | qproperty-line-pfx-lumina : #32CD32; /* Line prefix: Lumina */ 238 | } 239 | -------------------------------------------------------------------------------- /themes/colors/willi/user.css: -------------------------------------------------------------------------------- 1 | /* NOTE: This is an autogenerated file; please do not edit. */ 2 | 3 | CustomIDAMemo 4 | { 5 | qproperty-line-fg-insn: #2E3436; 6 | qproperty-line-bg-default: #D3D7CF; 7 | qproperty-graph-bg-top: #232729; 8 | qproperty-graph-bg-bottom: #232729; 9 | qproperty-graph-node-title-normal: #555753; 10 | qproperty-graph-node-title-selected: #215D9C; 11 | qproperty-graph-node-title-current: #215D9C; 12 | } 13 | 14 | CustomIDAMemo[debugging="true"] 15 | { 16 | qproperty-line-fg-insn: #2E3436; 17 | qproperty-graph-bg-top: #232729; 18 | qproperty-graph-bg-bottom: #232729; 19 | qproperty-graph-node-title-normal: #555753; 20 | qproperty-graph-node-title-selected: #215D9C; 21 | qproperty-graph-node-title-current: #215D9C; 22 | } 23 | 24 | CustomIDAMemo[hints="true"] 25 | { 26 | qproperty-line-fg-insn: #2E3436; 27 | qproperty-graph-bg-top: #232729; 28 | qproperty-graph-bg-bottom: #232729; 29 | qproperty-graph-node-title-normal: #555753; 30 | qproperty-graph-node-title-selected: #215D9C; 31 | qproperty-graph-node-title-current: #215D9C; 32 | } -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/icons/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/adwaita-dark/icons/expand.png -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/icons/spacer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/adwaita-dark/icons/spacer.png -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/manifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/adwaita-dark/preview.png -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/readme.md: -------------------------------------------------------------------------------- 1 | # adwaita-dark 2 | 3 | an IDA Pro skin based on gnome3's adwaita-dark variant. 4 | use with the corresponding color theme. 5 | 6 | 7 | ## screenshot 8 | 9 | ![screenshot](preview.png?raw=true "adwaita-dark") 10 | 11 | 12 | ## colors 13 | 14 | ``` 15 | color palatte: 16 | 17 | (darkest) 18 | dark-0, light-7 #1B1F20 19 | dark-1, light-6 #2D3234 20 | dark-2, light-5 #2E3436 21 | dark-3, light-4 #33393B 22 | dark-4, light-3 #919494 23 | dark-5, light-2 #D3D7CF 24 | dark-6, light-1 #EEEEEC 25 | dark-7, light-0 #FFFFFF 26 | (lightest) 27 | 28 | accent #215D9C 29 | ``` 30 | -------------------------------------------------------------------------------- /themes/skins/adwaita-dark/stylesheet.qss: -------------------------------------------------------------------------------- 1 | /*** 2 | color palatte: 3 | 4 | (darkest) 5 | dark-0, light-7 #1B1F20 6 | dark-1, light-6 #2D3234 7 | dark-2, light-5 #2E3436 8 | dark-3, light-4 #33393B 9 | dark-4, light-3 #919494 10 | dark-5, light-2 #D3D7CF 11 | dark-6, light-1 #EEEEEC 12 | dark-7, light-0 #FFFFFF 13 | (lightest) 14 | 15 | accent #215D9C 16 | */ 17 | 18 | 19 | QWidget { 20 | /* dark-3 */ 21 | background-color: #33393B; 22 | 23 | /* light-1 */ 24 | color: #EEEEEC; 25 | } 26 | 27 | QCheckBox { 28 | /* light-1 */ 29 | background-color: #EEEEEC; 30 | } 31 | 32 | QTextEdit { 33 | border-radius: 2px; 34 | 35 | /* dark-1 */ 36 | background-color: #2D3234; 37 | 38 | /* dark-0 */ 39 | border: 1px solid #1B1F20; 40 | } 41 | 42 | QMenuBar { 43 | /* dark-3 */ 44 | background-color: #33393B; 45 | } 46 | 47 | QMenuBar::item { 48 | /* dark-2 */ 49 | background-color: #2E3436; 50 | } 51 | 52 | 53 | QMenu::item:selected { 54 | /* accent */ 55 | background-color: #215D9C; 56 | } 57 | 58 | QLineEdit { 59 | min-height: 20px; 60 | border-radius: 2px; 61 | 62 | /* dark-0 */ 63 | border: 1px solid #1B1F20; 64 | } 65 | 66 | QLineEdit:focus { 67 | /* accent */ 68 | border: 1px solid #215D9C; 69 | } 70 | 71 | QTabBar::tab { 72 | /* dark-2 */ 73 | background-color: #2E3436; 74 | } 75 | 76 | QTabBar::tab:selected { 77 | /* dark-2 */ 78 | background-color: #2E3436; 79 | 80 | /* accent */ 81 | border-bottom: 2px solid #215D9C; 82 | } 83 | 84 | QHeaderView::section { 85 | /* dark-2 */ 86 | background-color: #2E3436; 87 | 88 | /* dark-1 */ 89 | border-left: 3px solid #2D3234; 90 | } 91 | 92 | QTableView { 93 | /* dark-0 */ 94 | border: 1px solid #1B1F20; 95 | 96 | /* dark-1 */ 97 | background-color: #2D3234; 98 | } 99 | 100 | QTableCornerButton::section { 101 | /* dark-1 */ 102 | background: #2D3234; 103 | 104 | /* dark-0 */ 105 | border: 2px outset #00000; 106 | } 107 | 108 | IDAView, hexview_t, CustomIDAMemo { 109 | border: none; 110 | } 111 | 112 | CustomIDAMemo, EditContainer { 113 | font-family: ""; 114 | font-size: ; 115 | font-style: ; 116 | font-weight: ; 117 | } 118 | 119 | IDAView { 120 | font-family: ""; 121 | font-size: ; 122 | font-style: ; 123 | font-weight: ; 124 | } 125 | 126 | hexview_t { 127 | font-family: ""; 128 | font-size: ; 129 | font-style: ; 130 | font-weight: ; 131 | } 132 | 133 | /* TODO: DEBUG_REGISTERS, OUTPUT_WINDOW */ 134 | 135 | QScrollBar { 136 | width: 10px; 137 | margin: 0 0 0 0; 138 | 139 | /* dark-3 */ 140 | background-color: #33393B; 141 | } 142 | 143 | QScrollBar::sub-line, QScrollBar::add-line { 144 | width: 0; 145 | height: 0; 146 | } 147 | 148 | QScrollBar::add-page, QScrollBar::sub-page { 149 | background: none; 150 | } 151 | 152 | QScrollBar::handle:vertical { 153 | min-height: 20px; 154 | } 155 | 156 | QScrollBar::handle:horizontal { 157 | min-width: 20px; 158 | } 159 | 160 | QScrollBar::handle { 161 | /* light-3 */ 162 | background-color: #919494; 163 | margin: 3px; 164 | border-radius: 3px; 165 | } 166 | 167 | QToolBar { 168 | border: none; 169 | } 170 | 171 | QPushButton { 172 | text-align: center; 173 | min-height: 20px; 174 | min-width: 50px; 175 | padding: 0 6px 0 6px; 176 | border-radius: 2px; 177 | 178 | /* dark-0 */ 179 | border: 1px solid #1B1F20; 180 | } 181 | 182 | QPushButton:hover, QPushButton:default { 183 | /* dark-1 */ 184 | border: 1px solid #2D3234; 185 | } 186 | 187 | QPushButton:pressed { 188 | /* accent */ 189 | border: 1px solid #215D9C; 190 | } 191 | 192 | QComboBox { 193 | border-radius: 2px; 194 | 195 | /* dark-0 */ 196 | border: 1px solid #1B1F20; 197 | } 198 | 199 | QComboBox > QLineEdit, QComboBox > QLineEdit:hover, QComboBox > QLineEdit:focus { 200 | border: none; 201 | min-height: default; 202 | } 203 | 204 | QComboBox:focus { 205 | /* accent */ 206 | border: 1px solid #215D9C; 207 | } 208 | 209 | QComboBox::drop-down { 210 | subcontrol-origin: padding; 211 | subcontrol-position: top right; 212 | width: 15px; 213 | 214 | border-left-width: 1px; 215 | border-left-style: solid; 216 | /* accent? */ 217 | border-left-color: #215D9C; 218 | } 219 | 220 | QComboBox::down-arrow { 221 | image: url(/icons/expand.png); 222 | } 223 | 224 | /* Close, maximize and undock button for dock widgets */ 225 | IDADockWidget > QWidget > QAbstractButton { 226 | /* no border */ 227 | } 228 | 229 | QRadioButton, QLabel, QCheckBox { 230 | background: transparent; 231 | } 232 | 233 | TNavBand > QPushButton, RegJumpButton { 234 | min-height: 0; 235 | min-width: 0; 236 | padding: 0 0 0 0; 237 | border: none; 238 | } 239 | 240 | EditContainer, ChooserContainer, QGroupBox, QListView, QTreeView { 241 | border-radius: 2px; 242 | 243 | /* dark-0 */ 244 | border: 1px solid #1B1F20; 245 | } 246 | 247 | QGroupBox { 248 | margin-top: 5px; 249 | } 250 | 251 | QGroupBox::title { 252 | subcontrol-origin: margin; 253 | subcontrol-position: top center; 254 | } 255 | 256 | /* Remove border from IDC/Python switch button */ 257 | CLIWidget > QGroupBox > QPushButton, 258 | CLIWidget > QGroupBox > QPushButton:hover, 259 | CLIWidget > QGroupBox > QPushButton:focus { 260 | border: none; 261 | } 262 | 263 | CLIWidget > QGroupBox { 264 | margin-top: 0; 265 | } 266 | 267 | QTreeView::item:selected, QListView::item:selected, QTableView::item:selected { 268 | /* accent */ 269 | background-color: #215D9C; 270 | } 271 | -------------------------------------------------------------------------------- /themes/skins/readme.md: -------------------------------------------------------------------------------- 1 | # skins 2 | 3 | skins you can use with the [zyantific/IDASkins](https://github.com/zyantific/IDASkins) plugin. 4 | -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/adwaita-dark.clr: -------------------------------------------------------------------------------- 1 | [DISASM] 2 | 000000 // 3 | ff0000 //Default color 4 | a46534 //Regular comment 5 | 808080 //Repeatable comment 6 | 808080 //Automatic comment 7 | 36342e //Instruction 8 | 800000 //Dummy Data Name 9 | a46534 //Regular Data Name 10 | a46534 //Demangled Name 11 | 800000 //Punctuation 12 | 069a4e //Char constant in instruction 13 | 00ff00 //String constant in instruction 14 | 069a4e //Numeric constant in instruction 15 | 2929ef //Void operand 16 | 069a4e //Code reference 17 | ff8080 //Data reference 18 | 0000ff //Code reference to tail byte 19 | 008080 //Data reference to tail byte 20 | 010101 //Error or problem 21 | c0c0c0 //Line prefix 22 | a46534 //Binary line prefix bytes 23 | a46534 //Extra line 24 | ff0000 //Alternative operand 25 | 808080 //Hidden name 26 | ff8080 //Library function name 27 | 008000 //Local variable name 28 | 800000 //Dummy code name 29 | a46534 //Assembler directive 30 | 800080 //Macro 31 | 069a4e //String constant in data directive 32 | 008000 //Char constant in data directive 33 | 069a4e //Numeric constant in data directive 34 | 800000 //Keywords 35 | 800000 //Register name 36 | 2929ef //Imported name 37 | 008080 //Segment name 38 | 800000 //Dummy unknown name 39 | cf9f72 //Regular code name 40 | 800000 //Regular unknown name 41 | a46534 //Collapsed line 42 | 000000 //Max color number 43 | cfd7d3 //Line prefix: library function 44 | eceeee //Line prefix: regular function 45 | ffff00 //Line prefix: instruction 46 | 000000 //Line prefix: data 47 | 000080 //Line prefix: unexplored 48 | 808080 //Line prefix: externs 49 | 008080 //Line prefix: current item 50 | 2929ef //Line prefix: current line 51 | 000000 //Punctuation 52 | ff0000 //Opcode bytes 53 | 000000 //Manual operand 54 | [NAVBAR] 55 | ffffaa //Library function 56 | e8a200 //Regular function 57 | 577ab9 //Instruction 58 | c0c0c0 //Data item 59 | 6bb6b6 //Unexplored 60 | ffa6ff //External symbol 61 | 5b5bff //Errors 62 | 000000 //Gaps 63 | 7fffff //Cursor 64 | 00aaff //Address 65 | [DEBUG] 66 | ffd060 //Current IP 67 | ffa0a0 //Current IP (Enabled) 68 | 408020 //Current IP (Disabled) 69 | ffffcc //Current IP (Unavailible) 70 | 0000ff //Address 71 | 00ff00 //Address (Enabled) 72 | 004080 //Address (Disabled) 73 | 0080ff //Address (Unavailible) 74 | 000000 //Registers 75 | ff0000 //Registers (Changed) 76 | 800080 //Registers (Edited) 77 | [ARROW] 78 | c0c0c0 //Jump in current function 79 | 0000ff //Jump external to function 80 | 000000 //Jump under the cursor 81 | 008000 //Jump target 82 | ff4040 //Register target 83 | [GRAPH] 84 | 292723 //Top color 85 | 292723 //Bottom color 86 | 535755 //Normal title 87 | 9c5d21 //Selected title 88 | 9c5d21 //Current title 89 | 00ffff //Group frame 90 | 000000 //Node shadow 91 | ffffcc //Highlight color 1 92 | ccffcc //Highlight color 2 93 | 0000ff //Foreign node 94 | ff0000 //Normal edge 95 | 008000 //Yes edge 96 | 0000ff //No edge 97 | ff00ff //Highlighted edge 98 | ffff00 //Current edge 99 | [MISC] 100 | eceeee //Message text 101 | 1e1e1e //Message background 102 | 404080 //Patched bytes 103 | 0080ff //Unsaved changes 104 | [OTHER] 105 | 4fe9fc //Highlight color 106 | eceeee //Hint color 107 | [SYNTAX] 108 | cf9f72 1 0 //Keyword 1 109 | cf9f72 1 0 //Keyword 2 110 | 0000cc 1 0 //Keyword 3 111 | 2929ef 0 0 //String 112 | 069a4e 1 1 //Comment 113 | cf9f72 1 0 //Preprocessor 114 | 8b8b00 1 0 //Number 115 | -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/blank.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/close.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/drag.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/expand.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/float.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/fullscreen.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/icons/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/icons/menu.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_name": "VSCode dark (wb)", 3 | "author": "Microsoft (ported by jinmo, edit by wb)", 4 | "version": "v0.1", 5 | "preview_image": "preview.png", 6 | "clr_file": "adwaita-dark.clr", 7 | "qss_file": "stylesheet.qss" 8 | } -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/idawilli/8f84df8f18ffd56adeb931c82ad5245861b43a10/themes/skins/vscode-dark-wb/preview.png -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/stylesheet.less: -------------------------------------------------------------------------------- 1 | @text : #D4D4D4; 2 | @border : #ccc; 3 | @text_lighter : #fff; 4 | @text_light : #e7e7e7; 5 | @sans_serif : 'Segoe UI', 6 | 'Helvetica Neue'; 7 | @monospace : 'Consolas', 8 | 'Menlo'; 9 | 10 | @tab_font_size: 14px; 11 | @ui_font_size : 14px; 12 | 13 | @background_primary : #1E1E1E; 14 | @background_menu : #252526; 15 | @background_color_4 : #3C3C3C; 16 | @background_color_5 : #333; 17 | @background_color_6 : #505050; 18 | @background_color_7 : #2D3234; 19 | @background_color_9 : #2D2D2D; 20 | @background_color_11: rgba(121, 121, 121, 0.4); 21 | @background_color_12: rgba(100, 100, 100, 0.7); 22 | @background_color_13: #094771; 23 | 24 | .disasm-font { 25 | font-family: ""; 26 | font-size : ; 27 | font-style : ; 28 | font-weight: ; 29 | } 30 | 31 | .hexview-font { 32 | font-family: ""; 33 | font-size : ; 34 | font-style : ; 35 | font-weight: ; 36 | } 37 | 38 | .text-input-font { 39 | font-family: ""; 40 | font-size : ; 41 | font-style : ; 42 | font-weight: ; 43 | } 44 | 45 | /* Common properties */ 46 | QWidget { 47 | background-color: @background_primary; 48 | color : @text; 49 | } 50 | 51 | IDAView { 52 | border : none; 53 | border-bottom: 1px solid #414141; 54 | .disasm-font; 55 | } 56 | 57 | hexview_t { 58 | border: none; 59 | .hexview-font; 60 | } 61 | 62 | CustomIDAMemo { 63 | border: none; 64 | .text-input-font; 65 | } 66 | 67 | QToolBar { 68 | /* Toolbar */ 69 | background-color: @background_color_5; 70 | border : none; 71 | padding : 8px; 72 | spacing : 8px; 73 | 74 | &:active { 75 | background-color: @background_color_4; 76 | } 77 | 78 | QWidget { 79 | background: transparent; 80 | } 81 | 82 | &:handle { 83 | image : url(/icons/drag.png); 84 | padding-right: 10px; 85 | padding-left : 0; 86 | width : 10px; 87 | } 88 | } 89 | 90 | IDAMainWindow { 91 | >QStatusBar { 92 | &:item { 93 | border: none; 94 | } 95 | 96 | QWidget { 97 | font-family : @monospace; 98 | color : @text_lighter; 99 | padding-left: 17px; 100 | } 101 | 102 | min-height : 0; 103 | height : 27px; 104 | padding-left: 7px; 105 | background : #007acc; 106 | } 107 | } 108 | 109 | QPushButton { 110 | /* Buttons */ 111 | font-family: @sans_serif; 112 | text-align : center; 113 | min-height : 20px; 114 | min-width : 50px; 115 | border : none; 116 | background : #0e639c; 117 | padding : 2px 14px; 118 | 119 | &:hover { 120 | background: #17b; 121 | border : 1px solid #2D3234; 122 | } 123 | 124 | &:default { 125 | border: 1px solid #2D3234; 126 | } 127 | 128 | &:pressed { 129 | border: 1px solid #215D9C; 130 | } 131 | } 132 | 133 | QTreeView { 134 | font-family: @sans_serif; 135 | border : 1px solid #1B1F20; 136 | 137 | &:item { 138 | font-family: @sans_serif; 139 | font-size : @ui_font_size; 140 | height : 27.5px; 141 | 142 | &:selected { 143 | background-color: @background_color_13; 144 | } 145 | } 146 | } 147 | 148 | QLineEdit { 149 | /* Single line edit */ 150 | font-family: @sans_serif; 151 | border : 1px solid transparent; 152 | background : #272727; 153 | color : @border; 154 | padding : 4px; 155 | 156 | &:focus { 157 | border: 1px solid #135785; 158 | } 159 | } 160 | 161 | /* Dock widget without tabs */ 162 | QTabBar { 163 | font-family : @sans_serif; 164 | background-color: @background_menu; 165 | 166 | &:tab { 167 | border-right : 1px solid #252526; 168 | background-color: @background_color_9; 169 | height : 43px; 170 | text-transform : uppercase; 171 | font-size : @tab_font_size; 172 | padding-left : 16px; 173 | padding-right : 16px; 174 | 175 | &:selected { 176 | background-color: @background_primary; 177 | } 178 | } 179 | 180 | &:close-button { 181 | &:hover { 182 | background: rgba(255, 255, 255, 0.1); 183 | } 184 | 185 | &:pressed { 186 | icon-size: 24px; 187 | padding : 1px -1px -1px 1px; 188 | } 189 | 190 | /* For toolbar */ 191 | image : url(/icons/close.png); 192 | icon-size: 20px; 193 | padding : 0px; 194 | } 195 | } 196 | 197 | QMenuBar { 198 | /* Menu bar & items */ 199 | font-family : @sans_serif; 200 | background-color: @background_color_5; 201 | padding-left : 2px; 202 | 203 | &:active { 204 | background-color: @background_color_4; 205 | } 206 | 207 | &:item { 208 | background: transparent; 209 | color : @border; 210 | padding : 8px 9px; 211 | 212 | &:selected { 213 | background-color: @background_color_6; 214 | } 215 | } 216 | } 217 | 218 | QTableView { 219 | /* Table and headers */ 220 | font-family : @sans_serif; 221 | border : 1px solid #1B1F20; 222 | background-color: @background_menu; 223 | 224 | &:item { 225 | font-family: @sans_serif; 226 | font-size : @ui_font_size; 227 | height : 27.5px; 228 | 229 | &:selected { 230 | border : none !important; 231 | background-color: @background_color_13; 232 | } 233 | } 234 | 235 | &:section { 236 | &:item { 237 | background: #1e1e1e !important; 238 | } 239 | } 240 | 241 | QHeaderView { 242 | &:section { 243 | background-color: @background_color_9; 244 | border : none; 245 | 246 | &:horizontal { 247 | padding : 5px; 248 | border-left: 1px solid #2D3234; 249 | } 250 | 251 | &:vertical { 252 | padding : 0; 253 | background: transparent; 254 | } 255 | } 256 | } 257 | 258 | QTableCornerButton { 259 | &:section { 260 | background: #2D3234; 261 | border : 0px outset #000; 262 | } 263 | } 264 | } 265 | 266 | QListView { 267 | &:item { 268 | font-family: @sans_serif; 269 | font-size : @ui_font_size; 270 | height : 27.5px; 271 | 272 | &:selected { 273 | background-color: @background_color_13; 274 | } 275 | } 276 | 277 | border: 1px solid #1B1F20; 278 | } 279 | 280 | QCheckBox { 281 | background-color: @text; 282 | } 283 | 284 | QCheckBox, 285 | QLabel, 286 | QRadioButton { 287 | background: transparent; 288 | } 289 | 290 | QMenu { 291 | background-color: @background_menu; 292 | padding-bottom : 8px; 293 | 294 | &:item { 295 | font-weight: 400; 296 | padding : 6px 40px 3px 40px; 297 | 298 | &:selected { 299 | background-color: @background_color_13; 300 | } 301 | } 302 | 303 | &:right-arrow { 304 | image : url(/icons/menu.png); 305 | margin: 5px 15px 5px 5px; 306 | } 307 | 308 | &:icon { 309 | padding-left: 20px; 310 | } 311 | 312 | &:separator { 313 | height : 1px; 314 | background: #616162; 315 | width : 1px; 316 | margin : 6px; 317 | } 318 | } 319 | 320 | QComboBox { 321 | /* Combobox which is editable or not editable */ 322 | background: #3c3c3c; 323 | border : 1px solid #1B1F20; 324 | padding : 4px; 325 | 326 | &:focus, 327 | &:on { 328 | border: 1px solid #135785; 329 | border: 1px solid transparent; 330 | } 331 | 332 | &:editable { 333 | border: 1px solid transparent; 334 | } 335 | 336 | QLineEdit { 337 | background: transparent; 338 | border : 0; 339 | padding : 0; 340 | } 341 | 342 | &:drop-down { 343 | subcontrol-origin : padding; 344 | subcontrol-position: top right; 345 | border : none; 346 | width : 20px; 347 | 348 | &:editable { 349 | subcontrol-origin : padding; 350 | subcontrol-position: top right; 351 | border : none; 352 | width : 20px; 353 | } 354 | } 355 | 356 | &:down-arrow { 357 | image : url(/icons/expand.png); 358 | margin: 0; 359 | } 360 | 361 | >QLineEdit { 362 | border : none; 363 | min-height: default; 364 | 365 | &:hover { 366 | border : none; 367 | min-height: default; 368 | } 369 | 370 | &:focus { 371 | border : none; 372 | min-height: default; 373 | } 374 | } 375 | 376 | QAbstractItemView { 377 | background : rgb(60, 60, 60); 378 | outline : none; 379 | selection-background-color: rgb(6, 47, 74); 380 | } 381 | 382 | &:focus { 383 | border: 1px solid #215D9C; 384 | } 385 | 386 | QLineEdit { 387 | &:focus { 388 | border-right: none; 389 | } 390 | } 391 | } 392 | 393 | QTextEdit { 394 | /* Multiple line edit */ 395 | background-color: @background_color_7; 396 | border : 1px solid #1B1F20; 397 | } 398 | 399 | TNavBand>QPushButton, 400 | nav_scroll_button_t { 401 | border : none; 402 | background : transparent; 403 | min-height : default; 404 | min-width : default; 405 | padding : default; 406 | border-radius: default; 407 | } 408 | 409 | RegJumpButton { 410 | min-height: 0; 411 | min-width : 0; 412 | border : none; 413 | padding : 0; 414 | } 415 | 416 | /* Dock widget which has tabs */ 417 | DockWidgetTitle { 418 | font : @ui_font_size @sans_serif; 419 | background : #252526; 420 | text-transform: uppercase; 421 | min-height : 0px; 422 | height : 43px; 423 | padding : 0px 5px 0px 5px; 424 | border : none; 425 | 426 | DockWidgetTitleButton { 427 | &:hover { 428 | background: rgba(255, 255, 255, 0.1); 429 | } 430 | 431 | color : @text_lighter; 432 | background: transparent; 433 | border : none; 434 | min-height: 0; 435 | min-width : 0; 436 | margin : 0; 437 | padding : 0; 438 | 439 | &[toolTip="Close"] { 440 | qproperty-icon: url(/icons/close.png); 441 | icon-size : 20px; 442 | } 443 | 444 | &[toolTip="Fullscreen"] { 445 | qproperty-icon: url(/icons/fullscreen.png); 446 | icon-size : 20px; 447 | } 448 | 449 | &[toolTip="Float"] { 450 | qproperty-icon: url(/icons/float.png); 451 | icon-size : 20px; 452 | } 453 | 454 | } 455 | 456 | } 457 | 458 | EditContainer { 459 | border: 1px solid #1B1F20; 460 | .text-input-font; 461 | } 462 | 463 | QScrollBar { 464 | /* Scrollbar */ 465 | background-color: @background_primary; 466 | border : 0px solid #383838; 467 | margin : 0; 468 | padding : 0; 469 | 470 | &:horizontal { 471 | border-top-width: 1px; 472 | height : 12px; 473 | } 474 | 475 | &:vertical { 476 | border-left-width: 1px; 477 | width : 12px; 478 | } 479 | 480 | &:sub-line { 481 | width : 0; 482 | height: 0; 483 | } 484 | 485 | &:add-line { 486 | width : 0; 487 | height: 0; 488 | } 489 | 490 | &:add-page { 491 | background: none; 492 | width : 0; 493 | height : 0; 494 | } 495 | 496 | &:sub-page { 497 | background: none; 498 | width : 0; 499 | height : 0; 500 | } 501 | 502 | &:handle { 503 | &:vertical { 504 | min-height: 20px; 505 | } 506 | 507 | &:horizontal { 508 | min-width: 20px; 509 | } 510 | 511 | background-color: @background_color_11; 512 | 513 | &:hover { 514 | background-color: @background_color_12; 515 | } 516 | } 517 | } 518 | 519 | ChooserContainer { 520 | border: 1px solid #1B1F20; 521 | } 522 | 523 | QGroupBox { 524 | border : 1px solid #1B1F20; 525 | margin-top: 5px; 526 | 527 | &:title { 528 | subcontrol-origin : margin; 529 | subcontrol-position: top center; 530 | } 531 | } 532 | 533 | TextArrows { 534 | border-bottom: 1px solid #414141; 535 | } 536 | 537 | IDADockWidget[objectName="Output window"] { 538 | /* Output window */ 539 | qproperty-windowIcon: url(/icons/blank.png); 540 | 541 | DockWidgetTitle { 542 | background: #1e1e1e; 543 | color : @text_light; 544 | margin : 0; 545 | } 546 | 547 | QTextEdit { 548 | &:focus { 549 | border: 1px solid #215D9C; 550 | } 551 | } 552 | 553 | QLineEdit { 554 | padding-bottom: 2px; 555 | } 556 | 557 | QGroupBox { 558 | QPushButton { 559 | border : none; 560 | text-transform: uppercase; 561 | min-width : 70px; 562 | 563 | &:hover { 564 | border: none; 565 | } 566 | 567 | &:focus { 568 | border: none; 569 | } 570 | } 571 | 572 | margin : 0; 573 | padding : 0; 574 | border : none; 575 | } 576 | 577 | } -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/stylesheet.qss: -------------------------------------------------------------------------------- 1 | .disasm-font { 2 | font-family: ""; 3 | font-size: ; 4 | font-style: ; 5 | font-weight: ; 6 | } 7 | .hexview-font { 8 | font-family: ""; 9 | font-size: ; 10 | font-style: ; 11 | font-weight: ; 12 | } 13 | .text-input-font { 14 | font-family: ""; 15 | font-size: ; 16 | font-style: ; 17 | font-weight: ; 18 | } 19 | /* Common properties */ 20 | QWidget { 21 | background-color: #1E1E1E; 22 | color: #D4D4D4; 23 | } 24 | IDAView { 25 | border: none; 26 | border-bottom: 1px solid #414141; 27 | font-family: ""; 28 | font-size: ; 29 | font-style: ; 30 | font-weight: ; 31 | } 32 | hexview_t { 33 | border: none; 34 | font-family: ""; 35 | font-size: ; 36 | font-style: ; 37 | font-weight: ; 38 | } 39 | CustomIDAMemo { 40 | border: none; 41 | font-family: ""; 42 | font-size: ; 43 | font-style: ; 44 | font-weight: ; 45 | } 46 | QToolBar { 47 | /* Toolbar */ 48 | background-color: #333; 49 | border: none; 50 | padding: 8px; 51 | spacing: 8px; 52 | } 53 | QToolBar:active { 54 | background-color: #3C3C3C; 55 | } 56 | QToolBar QWidget { 57 | background: transparent; 58 | } 59 | QToolBar:handle { 60 | image: url(/icons/drag.png); 61 | padding-right: 10px; 62 | padding-left: 0; 63 | width: 10px; 64 | } 65 | IDAMainWindow > QStatusBar { 66 | min-height: 0; 67 | height: 27px; 68 | padding-left: 7px; 69 | background: #007acc; 70 | } 71 | IDAMainWindow > QStatusBar:item { 72 | border: none; 73 | } 74 | IDAMainWindow > QStatusBar QWidget { 75 | font-family: 'Consolas', 'Menlo'; 76 | color: #fff; 77 | padding-left: 17px; 78 | } 79 | QPushButton { 80 | /* Buttons */ 81 | font-family: 'Segoe UI', 'Helvetica Neue'; 82 | text-align: center; 83 | min-height: 20px; 84 | min-width: 50px; 85 | border: none; 86 | background: #0e639c; 87 | padding: 2px 14px; 88 | } 89 | QPushButton:hover { 90 | background: #17b; 91 | border: 1px solid #2D3234; 92 | } 93 | QPushButton:default { 94 | border: 1px solid #2D3234; 95 | } 96 | QPushButton:pressed { 97 | border: 1px solid #215D9C; 98 | } 99 | QTreeView { 100 | font-family: 'Segoe UI', 'Helvetica Neue'; 101 | border: 1px solid #1B1F20; 102 | } 103 | QTreeView:item { 104 | font-family: 'Segoe UI', 'Helvetica Neue'; 105 | font-size: 14px; 106 | height: 27.5px; 107 | } 108 | QTreeView:item:selected { 109 | background-color: #094771; 110 | } 111 | QLineEdit { 112 | /* Single line edit */ 113 | font-family: 'Segoe UI', 'Helvetica Neue'; 114 | border: 1px solid transparent; 115 | background: #272727; 116 | color: #ccc; 117 | padding: 4px; 118 | } 119 | QLineEdit:focus { 120 | border: 1px solid #135785; 121 | } 122 | /* Dock widget without tabs */ 123 | QTabBar { 124 | font-family: 'Segoe UI', 'Helvetica Neue'; 125 | background-color: #252526; 126 | } 127 | QTabBar:tab { 128 | border-right: 1px solid #252526; 129 | background-color: #2D2D2D; 130 | height: 43px; 131 | text-transform: uppercase; 132 | font-size: 14px; 133 | padding-left: 16px; 134 | padding-right: 16px; 135 | } 136 | QTabBar:tab:selected { 137 | background-color: #1E1E1E; 138 | } 139 | QTabBar:close-button { 140 | /* For toolbar */ 141 | image: url(/icons/close.png); 142 | icon-size: 20px; 143 | padding: 0px; 144 | } 145 | QTabBar:close-button:hover { 146 | background: rgba(255, 255, 255, 0.1); 147 | } 148 | QTabBar:close-button:pressed { 149 | icon-size: 24px; 150 | padding: 1px -1px -1px 1px; 151 | } 152 | QMenuBar { 153 | /* Menu bar & items */ 154 | font-family: 'Segoe UI', 'Helvetica Neue'; 155 | background-color: #333; 156 | padding-left: 2px; 157 | } 158 | QMenuBar:active { 159 | background-color: #3C3C3C; 160 | } 161 | QMenuBar:item { 162 | background: transparent; 163 | color: #ccc; 164 | padding: 8px 9px; 165 | } 166 | QMenuBar:item:selected { 167 | background-color: #505050; 168 | } 169 | QTableView { 170 | /* Table and headers */ 171 | font-family: 'Segoe UI', 'Helvetica Neue'; 172 | border: 1px solid #1B1F20; 173 | background-color: #252526; 174 | } 175 | QTableView:item { 176 | font-family: 'Segoe UI', 'Helvetica Neue'; 177 | font-size: 14px; 178 | height: 27.5px; 179 | } 180 | QTableView:item:selected { 181 | border: none !important; 182 | background-color: #094771; 183 | } 184 | QTableView:section:item { 185 | background: #1e1e1e !important; 186 | } 187 | QTableView QHeaderView:section { 188 | background-color: #2D2D2D; 189 | border: none; 190 | } 191 | QTableView QHeaderView:section:horizontal { 192 | padding: 5px; 193 | border-left: 1px solid #2D3234; 194 | } 195 | QTableView QHeaderView:section:vertical { 196 | padding: 0; 197 | background: transparent; 198 | } 199 | QTableView QTableCornerButton:section { 200 | background: #2D3234; 201 | border: 0px outset #000; 202 | } 203 | QListView { 204 | border: 1px solid #1B1F20; 205 | } 206 | QListView:item { 207 | font-family: 'Segoe UI', 'Helvetica Neue'; 208 | font-size: 14px; 209 | height: 27.5px; 210 | } 211 | QListView:item:selected { 212 | background-color: #094771; 213 | } 214 | QCheckBox { 215 | background-color: #D4D4D4; 216 | } 217 | QCheckBox, 218 | QLabel, 219 | QRadioButton { 220 | background: transparent; 221 | } 222 | QMenu { 223 | background-color: #252526; 224 | padding-bottom: 8px; 225 | } 226 | QMenu:item { 227 | font-weight: 400; 228 | padding: 6px 40px 3px 40px; 229 | } 230 | QMenu:item:selected { 231 | background-color: #094771; 232 | } 233 | QMenu:right-arrow { 234 | image: url(/icons/menu.png); 235 | margin: 5px 15px 5px 5px; 236 | } 237 | QMenu:icon { 238 | padding-left: 20px; 239 | } 240 | QMenu:separator { 241 | height: 1px; 242 | background: #616162; 243 | width: 1px; 244 | margin: 6px; 245 | } 246 | QComboBox { 247 | /* Combobox which is editable or not editable */ 248 | background: #3c3c3c; 249 | border: 1px solid #1B1F20; 250 | padding: 4px; 251 | } 252 | QComboBox:focus, 253 | QComboBox:on { 254 | border: 1px solid #135785; 255 | border: 1px solid transparent; 256 | } 257 | QComboBox:editable { 258 | border: 1px solid transparent; 259 | } 260 | QComboBox QLineEdit { 261 | background: transparent; 262 | border: 0; 263 | padding: 0; 264 | } 265 | QComboBox:drop-down { 266 | subcontrol-origin: padding; 267 | subcontrol-position: top right; 268 | border: none; 269 | width: 20px; 270 | } 271 | QComboBox:drop-down:editable { 272 | subcontrol-origin: padding; 273 | subcontrol-position: top right; 274 | border: none; 275 | width: 20px; 276 | } 277 | QComboBox:down-arrow { 278 | image: url(/icons/expand.png); 279 | margin: 0; 280 | } 281 | QComboBox > QLineEdit { 282 | border: none; 283 | min-height: default; 284 | } 285 | QComboBox > QLineEdit:hover { 286 | border: none; 287 | min-height: default; 288 | } 289 | QComboBox > QLineEdit:focus { 290 | border: none; 291 | min-height: default; 292 | } 293 | QComboBox QAbstractItemView { 294 | background: #3c3c3c; 295 | outline: none; 296 | selection-background-color: #062f4a; 297 | } 298 | QComboBox:focus { 299 | border: 1px solid #215D9C; 300 | } 301 | QComboBox QLineEdit:focus { 302 | border-right: none; 303 | } 304 | QTextEdit { 305 | /* Multiple line edit */ 306 | background-color: #2D3234; 307 | border: 1px solid #1B1F20; 308 | } 309 | TNavBand > QPushButton, 310 | nav_scroll_button_t { 311 | border: none; 312 | background: transparent; 313 | min-height: default; 314 | min-width: default; 315 | padding: default; 316 | border-radius: default; 317 | } 318 | RegJumpButton { 319 | min-height: 0; 320 | min-width: 0; 321 | border: none; 322 | padding: 0; 323 | } 324 | /* Dock widget which has tabs */ 325 | DockWidgetTitle { 326 | font: 14px 'Segoe UI', 'Helvetica Neue'; 327 | background: #252526; 328 | text-transform: uppercase; 329 | min-height: 0px; 330 | height: 43px; 331 | padding: 0px 5px 0px 5px; 332 | border: none; 333 | } 334 | DockWidgetTitle DockWidgetTitleButton { 335 | color: #fff; 336 | background: transparent; 337 | border: none; 338 | min-height: 0; 339 | min-width: 0; 340 | margin: 0; 341 | padding: 0; 342 | } 343 | DockWidgetTitle DockWidgetTitleButton:hover { 344 | background: rgba(255, 255, 255, 0.1); 345 | } 346 | DockWidgetTitle DockWidgetTitleButton[toolTip="Close"] { 347 | qproperty-icon: url(/icons/close.png); 348 | icon-size: 20px; 349 | } 350 | DockWidgetTitle DockWidgetTitleButton[toolTip="Fullscreen"] { 351 | qproperty-icon: url(/icons/fullscreen.png); 352 | icon-size: 20px; 353 | } 354 | DockWidgetTitle DockWidgetTitleButton[toolTip="Float"] { 355 | qproperty-icon: url(/icons/float.png); 356 | icon-size: 20px; 357 | } 358 | EditContainer { 359 | border: 1px solid #1B1F20; 360 | font-family: ""; 361 | font-size: ; 362 | font-style: ; 363 | font-weight: ; 364 | } 365 | QScrollBar { 366 | /* Scrollbar */ 367 | background-color: #1E1E1E; 368 | border: 0px solid #383838; 369 | margin: 0; 370 | padding: 0; 371 | } 372 | QScrollBar:horizontal { 373 | border-top-width: 1px; 374 | height: 12px; 375 | } 376 | QScrollBar:vertical { 377 | border-left-width: 1px; 378 | width: 12px; 379 | } 380 | QScrollBar:sub-line { 381 | width: 0; 382 | height: 0; 383 | } 384 | QScrollBar:add-line { 385 | width: 0; 386 | height: 0; 387 | } 388 | QScrollBar:add-page { 389 | background: none; 390 | width: 0; 391 | height: 0; 392 | } 393 | QScrollBar:sub-page { 394 | background: none; 395 | width: 0; 396 | height: 0; 397 | } 398 | QScrollBar:handle { 399 | background-color: rgba(121, 121, 121, 0.4); 400 | } 401 | QScrollBar:handle:vertical { 402 | min-height: 20px; 403 | } 404 | QScrollBar:handle:horizontal { 405 | min-width: 20px; 406 | } 407 | QScrollBar:handle:hover { 408 | background-color: rgba(100, 100, 100, 0.7); 409 | } 410 | ChooserContainer { 411 | border: 1px solid #1B1F20; 412 | } 413 | QGroupBox { 414 | border: 1px solid #1B1F20; 415 | margin-top: 5px; 416 | } 417 | QGroupBox:title { 418 | subcontrol-origin: margin; 419 | subcontrol-position: top center; 420 | } 421 | TextArrows { 422 | border-bottom: 1px solid #414141; 423 | } 424 | IDADockWidget[objectName="Output window"] { 425 | /* Output window */ 426 | qproperty-windowIcon: url(/icons/blank.png); 427 | } 428 | IDADockWidget[objectName="Output window"] DockWidgetTitle { 429 | background: #1e1e1e; 430 | color: #e7e7e7; 431 | margin: 0; 432 | } 433 | IDADockWidget[objectName="Output window"] QTextEdit:focus { 434 | border: 1px solid #215D9C; 435 | } 436 | IDADockWidget[objectName="Output window"] QLineEdit { 437 | padding-bottom: 2px; 438 | } 439 | IDADockWidget[objectName="Output window"] QGroupBox { 440 | margin: 0; 441 | padding: 0; 442 | border: none; 443 | } 444 | IDADockWidget[objectName="Output window"] QGroupBox QPushButton { 445 | border: none; 446 | text-transform: uppercase; 447 | min-width: 70px; 448 | } 449 | IDADockWidget[objectName="Output window"] QGroupBox QPushButton:hover { 450 | border: none; 451 | } 452 | IDADockWidget[objectName="Output window"] QGroupBox QPushButton:focus { 453 | border: none; 454 | } 455 | -------------------------------------------------------------------------------- /themes/skins/vscode-dark-wb/vscode-dark.clr: -------------------------------------------------------------------------------- 1 | [DISASM] 2 | 000000 // 3 | 55996a //Default color 4 | 55996a //Regular comment 5 | 808080 //Repeatable comment 6 | 808080 //Automatic comment 7 | d4d4d4 //Instruction 8 | fedc9c //Dummy Data Name 9 | fedc9c //Regular Data Name 10 | aadcdc //Demangled Name 11 | d4d4d4 //Punctuation 12 | 7dbad7 //Char constant in instruction 13 | 00ff00 //String constant in instruction 14 | 55996a //Numeric constant in instruction 15 | 7dbad7 //Void operand 16 | 55996a //Code reference 17 | d69c56 //Data reference 18 | 0014e5 //Code reference to tail byte 19 | 008080 //Data reference to tail byte 20 | 010101 //Error or problem 21 | c0c0c0 //Line prefix 22 | a8ceb5 //Binary line prefix bytes 23 | aadcdc //Extra line 24 | ff0000 //Alternative operand 25 | d4d4d4 //Hidden name 26 | fedc9c //Library function name 27 | fedc9c //Local variable name 28 | fedc9c //Dummy code name 29 | fedc9c //Assembler directive 30 | c086c5 //Macro 31 | 7891ce //String constant in data directive 32 | 7891ce //Char constant in data directive 33 | 55996a //Numeric constant in data directive 34 | c086c5 //Keywords 35 | fedc9c //Register name 36 | c086c5 //Imported name 37 | d4d4d4 //Segment name 38 | fedc9c //Dummy unknown name 39 | aadcdc //Regular code name 40 | ffff00 //Regular unknown name 41 | 55996a //Collapsed line 42 | 000000 //Max color number 43 | 1e1e1e //Line prefix: library function 44 | 784f26 //Line prefix: regular function 45 | ffff00 //Line prefix: instruction 46 | d69c56 //Line prefix: data 47 | 000080 //Line prefix: unexplored 48 | 858585 //Line prefix: externs 49 | 008080 //Line prefix: current item 50 | ff00ff //Line prefix: current line 51 | 000000 //Punctuation 52 | d4d4d4 //Opcode bytes 53 | 000000 //Manual operand 54 | 32cd32 //Error 55 | [NAVBAR] 56 | ffffaa //Library function 57 | e8a200 //Regular function 58 | 577ab9 //Instruction 59 | c0c0c0 //Data item 60 | 6bb6b6 //Unexplored 61 | c086c5 //External symbol 62 | 5b5bff //Errors 63 | 000000 //Gaps 64 | 7fffff //Cursor 65 | 00aaff //Address 66 | 32cd32 //Lumina function 67 | [DEBUG] 68 | 184b4b //Current IP 69 | ffa0a0 //Current IP (+ enabled breakpoint) 70 | 408020 //Current IP (+ disabled breakpoint) 71 | 1e1e1e //Default background 72 | 0014e5 //Address (+ enabled breakpoint) 73 | 00ff00 //Address (+ disabled breakpoint) 74 | 004080 //Current IP (+ unavailable breakpoint) 75 | 0080ff //Address (+ unavailable breakpoint) 76 | 858585 //Registers 77 | d69c56 //Registers (changed) 78 | 800080 //Registers (edited) 79 | 808080 //Registers (unavailable) 80 | [ARROW] 81 | c0c0c0 //Jump in current function 82 | 0000ff //Jump external to function 83 | 000000 //Jump under the cursor 84 | 008000 //Jump target 85 | ff4040 //Register target 86 | [GRAPH] 87 | 3c3c3c //Top color 88 | 3c3c3c //Bottom color 89 | 4b4b4b //Normal title 90 | f9f9b1 //Selected title 91 | cfcfa0 //Current title 92 | 00ffff //Group frame 93 | 000000 //Node shadow 94 | ffffcc //Highlight color 1 95 | ccffcc //Highlight color 2 96 | 0000ff //Foreign node 97 | ff0000 //Normal edge 98 | 008000 //Yes edge 99 | 0000ff //No edge 100 | ff00ff //Highlighted edge 101 | ffff00 //Current edge 102 | [MISC] 103 | cccccc //Message text 104 | 1e1e1e //Message background 105 | 404080 //Patched bytes 106 | 0080ff //Unsaved changes 107 | [OTHER] 108 | 403a34 //Highlight color 109 | 262525 //Hint color 110 | [SYNTAX] 111 | ff0000 0 0 //Keyword 1 112 | 800080 0 0 //Keyword 2 113 | 0000ff 0 0 //Keyword 3 114 | 00008b 0 0 //String 115 | 006400 0 1 //Comment 116 | ff0000 1 0 //Preprocessor 117 | 8b8b00 1 0 //Number 118 | --------------------------------------------------------------------------------