├── .github
├── actions
│ └── install-patcherex2
│ │ └── action.yml
└── workflows
│ ├── ci.yml
│ ├── docker.yml
│ ├── docs.yml
│ └── pypi.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── docs
├── .pages
├── _css
│ └── extra.css
├── advanced_usages
│ ├── add_new_target_support.md
│ └── advanced_usage.md
├── api_references
│ ├── patcherex.md
│ └── patches.md
├── core_ideas
│ └── patches.md
├── examples
│ ├── insert_instruction_patch.md
│ ├── modify_function_patch.md
│ └── multiple_patches.md
└── index.md
├── examples
├── insert_instruction_patch
│ ├── add
│ ├── add.c
│ └── patch.py
├── insert_instruction_patch_c
│ ├── add
│ ├── add.c
│ └── patch.py
├── modify_function_patch
│ ├── add.c
│ └── patch.py
└── multiple_patches
│ ├── getline
│ ├── getline.c
│ └── patch.py
├── mkdocs.yml
├── patcherex2.png
├── pyproject.toml
├── src
└── patcherex2
│ ├── __init__.py
│ ├── components
│ ├── allocation_managers
│ │ ├── __init__.py
│ │ └── allocation_manager.py
│ ├── archinfo
│ │ ├── __init__.py
│ │ ├── aarch64.py
│ │ ├── amd64.py
│ │ ├── arm.py
│ │ ├── mips.py
│ │ ├── mips64.py
│ │ ├── ppc.py
│ │ ├── ppc64.py
│ │ ├── ppc_vle.py
│ │ ├── s390x.py
│ │ ├── sparc.py
│ │ └── x86.py
│ ├── assemblers
│ │ ├── __init__.py
│ │ ├── assembler.py
│ │ ├── bcc.py
│ │ ├── keystone.py
│ │ ├── keystone_arm.py
│ │ ├── keystone_sparc.py
│ │ └── ppc_vle.py
│ ├── assets
│ │ ├── .gitignore
│ │ └── assets.py
│ ├── binary_analyzers
│ │ ├── __init__.py
│ │ ├── angr.py
│ │ ├── binary_analyzer.py
│ │ ├── ghidra.py
│ │ └── ida.py
│ ├── binfmt_tools
│ │ ├── __init__.py
│ │ ├── binary.py
│ │ ├── binfmt_tool.py
│ │ ├── elf.py
│ │ └── ihex.py
│ ├── compilers
│ │ ├── __init__.py
│ │ ├── bcc.py
│ │ ├── clang.py
│ │ ├── clang_arm.py
│ │ ├── compiler.py
│ │ ├── llvm_recomp.py
│ │ ├── llvm_recomp_arm.py
│ │ └── ppc_vle.py
│ ├── disassemblers
│ │ ├── __init__.py
│ │ ├── capstone.py
│ │ ├── capstone_arm.py
│ │ ├── disassembler.py
│ │ └── ppc_vle.py
│ ├── patch_managers
│ │ ├── __init__.py
│ │ ├── builtin.py
│ │ ├── imp.py
│ │ └── patch_manager.py
│ └── utils
│ │ ├── __init__.py
│ │ └── utils.py
│ ├── patcherex.py
│ ├── patches
│ ├── __init__.py
│ ├── data_patches.py
│ ├── dummy_patches.py
│ ├── function_patches.py
│ ├── instruction_patches.py
│ ├── patch.py
│ └── raw_patches.py
│ └── targets
│ ├── __init__.py
│ ├── bin_arm_bare.py
│ ├── elf_aarch64_linux.py
│ ├── elf_amd64_linux.py
│ ├── elf_amd64_linux_recomp.py
│ ├── elf_arm_bare.py
│ ├── elf_arm_linux.py
│ ├── elf_arm_linux_recomp.py
│ ├── elf_arm_mimxrt1052.py
│ ├── elf_leon3_bare.py
│ ├── elf_mips64_linux.py
│ ├── elf_mips64el_linux.py
│ ├── elf_mips_linux.py
│ ├── elf_mipsel_linux.py
│ ├── elf_ppc64_linux.py
│ ├── elf_ppc64le_linux.py
│ ├── elf_ppc_linux.py
│ ├── elf_s390x_linux.py
│ ├── elf_x86_linux.py
│ ├── ihex_ppc_bare.py
│ └── target.py
└── tests
├── test_aarch64.py
├── test_arm.py
├── test_binaries
├── aarch64
│ ├── iip_c
│ ├── iip_c.c
│ ├── iip_c_asm_header
│ ├── iip_c_asm_header.c
│ ├── iip_c_float
│ ├── iip_c_float.c
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── amd64
│ ├── iip_c
│ ├── iip_c.c
│ ├── iip_c_asm_header
│ ├── iip_c_asm_header.c
│ ├── iip_c_float
│ ├── iip_c_float.c
│ ├── issue8
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── armhf
│ ├── iip_c
│ ├── iip_c.c
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── mips
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── mips64
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── mips64el
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── mipsel
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── ppc
│ ├── printf_nopie
│ └── replace_function_patch
├── ppc64
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── ppc64le
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── s390x
│ ├── printf.c
│ ├── printf_nopie
│ ├── printf_pie
│ ├── replace_function_patch
│ └── replace_function_patch.c
└── x86
│ ├── printf_nopie
│ ├── printf_pie
│ └── replace_function_patch
├── test_i386.py
├── test_mips.py
├── test_mips64.py
├── test_mips64el.py
├── test_mipsel.py
├── test_ppc.py
├── test_ppc64.py
├── test_ppc64le.py
├── test_s390x.py
└── test_x86_64.py
/.github/actions/install-patcherex2/action.yml:
--------------------------------------------------------------------------------
1 | name: "Install Patcherex2"
2 | runs:
3 | using: "composite"
4 | steps:
5 | - name: Set up Python 3
6 | uses: actions/setup-python@v5
7 | with:
8 | python-version: "3.10"
9 | - name: Install dependencies
10 | shell: bash
11 | run: |
12 | sudo apt-get update
13 | sudo apt-get install -y \
14 | clang-15 lld-15 \
15 | qemu-user \
16 | gcc-multilib \
17 | libc6-dev-armhf-cross libc6-dev-arm64-cross \
18 | libc6-dev-mips-cross libc6-dev-mips64-cross \
19 | libc6-dev-powerpc-cross libc6-dev-ppc64-cross \
20 | libc6-dev-mipsel-cross libc6-dev-mips64el-cross \
21 | libc6-dev-ppc64el-cross libc6-s390x-cross \
22 | - name: Install clang-19 lld-19
23 | shell: bash
24 | run: |
25 | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
26 | echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list
27 | sudo apt-get update
28 | sudo apt-get install -y clang-19 lld-19
29 | - name: Install Ghidra
30 | shell: bash
31 | run: |
32 | cd $HOME
33 | wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.0.3_build/ghidra_11.0.3_PUBLIC_20240410.zip
34 | unzip ghidra_11.0.3_PUBLIC_20240410.zip
35 | echo GHIDRA_INSTALL_DIR=$PWD/ghidra_11.0.3_PUBLIC >> $GITHUB_ENV
36 | - name: Install Patcherex2
37 | shell: bash
38 | run: |
39 | python3 -m pip install --upgrade pip
40 | python3 -m pip install -e .[all]
41 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: [ main ]
4 | pull_request:
5 | workflow_dispatch:
6 | schedule:
7 | - cron: '0 14 * * 1' # Runs at 2 PM UTC every Monday
8 |
9 | jobs:
10 | ci:
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | task: [test, lint, format, private-test]
15 | runs-on: ubuntu-22.04
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | - name: Install Patcherex2
20 | uses: ./.github/actions/install-patcherex2
21 | - name: Install Packages
22 | run: python3 -m pip install --upgrade pytest ruff
23 | - name: Run pytest
24 | if: matrix.task == 'test'
25 | run: python3 -m pytest
26 | - name: Run formatter
27 | if: matrix.task == 'format'
28 | run: python3 -m ruff format . --check
29 | - name: Run linter
30 | if: matrix.task == 'lint'
31 | run: python3 -m ruff check .
32 | - name: Checkout Private Tests
33 | if: matrix.task == 'private-test' && github.event_name != 'pull_request'
34 | uses: actions/checkout@v4
35 | with:
36 | repository: purseclab/patcherex2-private
37 | ssh-key: ${{ secrets.PRIVATE_TESTS_DEPLOY_KEY }}
38 | path: private
39 | - name: Run pytest for Private Tests
40 | if: matrix.task == 'private-test' && github.event_name != 'pull_request'
41 | working-directory: ./private
42 | run: python3 -m pytest -q --no-summary
43 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [ created ]
4 |
5 | env:
6 | REGISTRY: ghcr.io
7 | IMAGE_NAME: ${{ github.repository }}
8 |
9 | jobs:
10 | Publish:
11 | runs-on: ubuntu-latest
12 |
13 | permissions:
14 | contents: read
15 | packages: write
16 |
17 | steps:
18 | - name: Checkout Repository
19 | uses: actions/checkout@v4
20 |
21 | - name: Log in to GitHub Container Registry
22 | uses: docker/login-action@v3
23 | with:
24 | registry: ${{ env.REGISTRY }}
25 | username: ${{ github.actor }}
26 | password: ${{ secrets.GITHUB_TOKEN }}
27 |
28 | - name: Extract metadata
29 | id: meta
30 | uses: docker/metadata-action@v5
31 | with:
32 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
33 |
34 | - name: Build and push Docker image
35 | uses: docker/build-push-action@v5
36 | with:
37 | context: .
38 | push: true
39 | tags: ${{ steps.meta.outputs.tags }}
40 | labels: ${{ steps.meta.outputs.labels }}
41 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [ created ]
4 | workflow_dispatch:
5 |
6 | permissions:
7 | contents: read
8 | pages: write
9 | id-token: write
10 |
11 | jobs:
12 | deploy:
13 | environment:
14 | name: github-pages
15 | url: ${{ steps.deployment.outputs.page_url }}
16 | runs-on: ubuntu-22.04
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | - name: Install Patcherex2
21 | uses: ./.github/actions/install-patcherex2
22 | - name: Build Docs
23 | run: |
24 | python3 -m pip install mkdocs mkdocstrings[python] mkdocs-material mkdocs-awesome-pages-plugin
25 | python3 -m mkdocs build -d mkdocs_build -s
26 | - name: Setup Pages
27 | uses: actions/configure-pages@v4
28 | - name: Upload artifact
29 | uses: actions/upload-pages-artifact@v3
30 | with:
31 | path: mkdocs_build
32 | - name: Deploy to GitHub Pages
33 | id: deployment
34 | uses: actions/deploy-pages@v4
35 |
--------------------------------------------------------------------------------
/.github/workflows/pypi.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [ created ]
4 |
5 | jobs:
6 | Publish:
7 | name: Publish to PyPI
8 | runs-on: ubuntu-latest
9 | permissions:
10 | id-token: write
11 | steps:
12 | - name: Checkout Repository
13 | uses: actions/checkout@v4
14 | - name: Set up Python 3
15 | uses: actions/setup-python@v5
16 | with:
17 | python-version: "3.10"
18 | - name: Install pypa/build
19 | run: >-
20 | python -m
21 | pip install
22 | build
23 | --user
24 | - name: Build a binary wheel and a source tarball
25 | run: >-
26 | python -m
27 | build
28 | --sdist
29 | --wheel
30 | --outdir dist/
31 | .
32 | - name: Publish to PyPI
33 | uses: pypa/gh-action-pypi-publish@release/v1
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .ruff_cache/
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 | cover/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | .pybuilder/
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | # For a library or package, you might want to ignore these files since the code is
89 | # intended to run in multiple environments; otherwise, check them in:
90 | # .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # poetry
100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
101 | # This is especially recommended for binary packages to ensure reproducibility, and is more
102 | # commonly ignored for libraries.
103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
104 | #poetry.lock
105 |
106 | # pdm
107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
108 | #pdm.lock
109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
110 | # in version control.
111 | # https://pdm.fming.dev/#use-with-ide
112 | .pdm.toml
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | #.idea/
163 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04
2 |
3 | ARG DEBIAN_FRONTEND=noninteractive
4 |
5 | RUN apt-get update && apt-get install -y \
6 | git wget unzip \
7 | virtualenvwrapper python3-dev python3-pip python-is-python3 python3-venv \
8 | openjdk-17-jdk \
9 | clang-15 lld-15 \
10 | qemu-user \
11 | gcc-multilib \
12 | libc6-armhf-cross libc6-arm64-cross \
13 | libc6-mips-cross libc6-mips64-cross \
14 | libc6-powerpc-cross libc6-powerpc-ppc64-cross \
15 | libc6-mipsel-cross libc6-mips64el-cross \
16 | libc6-ppc64el-cross libc6-s390x-cross \
17 | && rm -rf /var/lib/apt/lists/*
18 |
19 | RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
20 | && echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main" | tee /etc/apt/sources.list.d/llvm.list \
21 | && apt-get update && apt-get install -y clang-19 lld-19 \
22 | && rm -rf /var/lib/apt/lists/*
23 |
24 | RUN wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.0.3_build/ghidra_11.0.3_PUBLIC_20240410.zip \
25 | && unzip /ghidra_11.0.3_PUBLIC_20240410.zip
26 |
27 | ENV GHIDRA_INSTALL_DIR=/ghidra_11.0.3_PUBLIC
28 |
29 | COPY . /patcherex2
30 |
31 | RUN pip install -U pip pytest ruff
32 | RUN pip install -e /patcherex2[all]
33 |
34 | CMD ["/bin/bash"]
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023, PurSec Lab
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Patcherex2 is a rewritten adaptation of the original [Patcherex](https://github.com/angr/patcherex) project, aimed at building upon its core ideas and extending its capabilities.
19 |
20 | ## Installation
21 |
22 | Patcherex2 is available on PyPI and can be installed using pip. Alternatively, you can use the provided Docker image.
23 |
24 | ### pip
25 | ```bash
26 | pip install patcherex2
27 | ```
28 |
29 | Install from latest commit
30 |
31 | ```bash
32 | pip install git+https://github.com/purseclab/Patcherex2.git
33 | ```
34 |
35 |
36 | ### Docker
37 | ```bash
38 | docker run --rm -it -v ${PWD}:/workdir -w /workdir ghcr.io/purseclab/patcherex2
39 | ```
40 |
41 |
42 | Build from latest commit
43 |
44 | ```bash
45 | docker build -t patcherex2 --platform linux/amd64 https://github.com/purseclab/Patcherex2.git
46 | docker run --rm -it -v ${PWD}:/workdir -w /workdir patcherex2
47 | ```
48 |
49 |
50 |
51 | ## Usage
52 | You can find usage examples [here](https://purseclab.github.io/Patcherex2/examples/insert_instruction_patch/).
53 |
54 |
55 | ## Documentation
56 | General documentation and API reference for Patcherex2 can be found at [purseclab.github.io/Patcherex2](https://purseclab.github.io/Patcherex2/).
57 |
58 |
59 | ## Supported Targets
60 |
61 | | | Linux x86 | Linux amd64 | Linux arm | Linux aarch64 | Linux PowerPC (32bit) | Linux PowerPC (64bit) | Linux PowerPCle (64bit) | Linux MIPS (32bit) | Linux MIPS (64bit) | Linux MIPSEL
(32bit) | Linux MIPSEL
(64bit) | Linux s390x | SPARCv8 (LEON3) | PowerPC (VLE) (IHEX)
62 | |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
63 | InsertDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
64 | RemoveDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
65 | ModifyDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
66 | InsertInstructionPatch (ASM) | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
67 | InsertInstructionPatch (C) | 🟥 | 🟩 | 🟥 | 🟩 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 |
68 | RemoveInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
69 | ModifyInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
70 | InsertFunctionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟨 | ⬜ | ⬜ |
71 | ModifyFunctionPatch | 🟨 | 🟩 | 🟩 | 🟩 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | ⬜ | ⬜ |
72 |
73 | 🟩 Fully Functional, 🟨 Limited Functionality, 🟥 Not Working, ⬜ Not Tested, 🟪 Work in Progress
74 |
75 |
76 | ## Acknowledgements
77 | This project was developed as part of the [DARPA AMP](https://www.darpa.mil/program/assured-micropatching) program, under contract N6600120C4031.
78 |
79 |
--------------------------------------------------------------------------------
/docs/.pages:
--------------------------------------------------------------------------------
1 | nav:
2 | - Home: index.md
3 | - Examples: examples
4 | - Core Ideas: core_ideas
5 | - Advanced Usages: advanced_usages
6 | - API References: api_references
7 | - ...
8 |
9 |
--------------------------------------------------------------------------------
/docs/_css/extra.css:
--------------------------------------------------------------------------------
1 | [data-md-color-scheme="pursec"] {
2 | --md-primary-fg-color: #4f6da9;
3 | --md-accent-fg-color: #bd9020;
4 | }
5 |
--------------------------------------------------------------------------------
/docs/advanced_usages/add_new_target_support.md:
--------------------------------------------------------------------------------
1 | # Adding New Target Support
2 |
3 | Patcherex2 has been designed with extensibility in mind, making it easy to add support for new targets. This document will walk you through the process of defining a new target in Patcherex2.
4 |
5 | ## Defining a New Target
6 |
7 | The first step is to define a new target class that inherits from the `Target` base class. This class should specify the required components to support the target. Here's an example of the existing `elf_amd64_linux` target definition.
8 |
9 | ```python title="src/patcherex2/targets/elf_amd64_linux.py"
10 | --8<-- "src/patcherex2/targets/elf_amd64_linux.py"
11 | ```
12 |
13 | ### `detect_target` Method
14 |
15 | The `detect_target` static method is responsible for automatically detecting if a given binary is supported by this target. It should return `True` if the binary matches the target criteria, or `False` otherwise. In the example above, it checks if the file is an ELF binary for the AMD64 architecture.
16 |
17 | ### `get_{component}` Methods
18 |
19 | The target definition should define methods to get the required components. The method names should be in the format `get_{component}`. The following are the list of components that must be defined for a target:
20 |
21 | - assembler
22 | - disassembler
23 | - compiler
24 | - binary_analyzer (Extract extra information from the binary file)
25 | - allocation_manager (Find free space or allocate new space in the binary)
26 | - binfmt_tool (Parse and modify binary formats, such as ELF, PE, IHEX, etc.)
27 | - utils
28 | - archinfo (Architecture specific information, such as register names, sizes, etc.)
29 |
30 | These methods allow you to specify the appropriate implementation for each component based on the target's requirements. Patcherex2 provides multiple implementations for common components that you can choose from.
31 |
32 | ##### Adding New Components
33 |
34 | If your target requires custom components not provided by Patcherex2, you can define new component classes that inherit from the respective base component classes. These custom components should implement the necessary methods to support your target's specific needs.
35 |
36 | ## Registering the New Target
37 |
38 | Once you have defined your target class, Patcherex2 will automatically register it if it is defined before creating a Patcherex2 instance (`p = Patcherex("/path/to/bin")`). Patcherex2 will call the `detect_target` method of each registered target to determine the appropriate target for the given binary.
39 |
40 | ## Manually Selecting the Target
41 |
42 | If your target is designed for manual selection only (i.e., `detect_target` always returns `False`), or if you want to override the automatic target detection, you can specify the target class when creating the Patcherex2 instance:
43 |
44 | ```python
45 | p = Patcherex("/path/to/binary", target_cls=MyCustomTarget)
46 | ```
47 |
48 | ## Configuring the Target
49 |
50 | ### Selecting Component Implementations
51 |
52 | Some targets may support multiple implementations for a given component, allowing you to choose the desired implementation. You can configure the target by passing a configuration dictionary to the Patcherex2 constructor.
53 |
54 | For example, if your target's `get_assembler` method supports multiple assemblers:
55 |
56 | ```python
57 | def get_assembler(self, assembler):
58 | assembler = assembler or "keystone"
59 | if assembler == "keystone":
60 | return Keystone()
61 | elif assembler == "gas":
62 | return Gas()
63 | raise NotImplementedError()
64 | ```
65 |
66 | You can select the assembler like this:
67 |
68 | ```python
69 | p = Patcherex("/path/to/binary", target_opts={"assembler": "gas"})
70 | ```
71 |
72 | This will use the `Gas` assembler instead of the default `Keystone` assembler.
73 |
74 | ### Configuring Components
75 |
76 | Some component implementations accept additional keyword arguments for configuration. You can pass these options through the `component_opts` parameter when creating the Patcherex2 instance.
77 |
78 | For example, if your target's `get_assembler` method accepts keyword arguments:
79 |
80 | ```python
81 | def get_assembler(self, assembler, **kwargs):
82 | assembler = assembler or "some_assembler"
83 | if assembler == "some_assembler":
84 | return SomeAssembler(**kwargs)
85 | raise NotImplementedError()
86 | ```
87 |
88 | You can configure the assembler options like this:
89 |
90 | ```python
91 | p = Patcherex("/path/to/binary", component_opts={"assembler": {"arch": "x86", "mode": "64"}})
92 | ```
93 |
94 | This will create the `SomeAssembler` instance with the provided keyword arguments:
95 |
96 | ```python
97 | SomeAssembler(arch="x86", mode="64")
98 | ```
99 |
100 | By following these steps and leveraging the extensible architecture of Patcherex2, you can easily add support for new targets and customize their behavior to suit your specific requirements.
101 |
--------------------------------------------------------------------------------
/docs/advanced_usages/advanced_usage.md:
--------------------------------------------------------------------------------
1 | # Advanced Usage
2 |
3 | ## Reuse Unreachable Code Locations
4 | Patcherex2 can be used to reuse unreachable code locations in the binary.
5 | Add the following code anywhere before `apply_patches` to reuse unreachable code.
6 |
7 | ```python
8 | for func in p.binary_analyzer.get_unused_funcs():
9 | p.allocation_manager.add_free_space(func["addr"], func["size"], "RX")
10 | ```
11 |
12 | ## Pre- and Post- Function Hooks
13 | Patcherex2 allows you to add pre- and post- function hooks to the function call when using `InsertFunctionPatch` and first argument is a address.
14 |
15 | ```python
16 | InsertFunctionPatch(0xdeadbeef, "int foo(int a) { return bar(); }", prefunc="mov rdi, 0x10", postfunc="mov rdi, rax")
17 | ```
18 | At the address `0xdeadbeef`, pre-function hook `mov rdi, 0x10` will be executed before the function `foo` is called and post-function hook `mov rdi, rax` will be executed after the function `foo` is called. This is useful when you want to pass arguments to the function or get the return value from the function.
19 |
20 | ## Save Context and Restore Context when using `Insert*Patch`
21 | When using `InsertInstructionPatch` or `InsertFunctionPatch`, it is possible to save the context before the inserted content and restore the context after the inserted content. This is useful when the inserted content modifies the context.
22 |
23 | ```python
24 | InsertInstructionPatch(0xdeadbeef, "push rbp", save_context=True)
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/api_references/patcherex.md:
--------------------------------------------------------------------------------
1 | :::patcherex2.patcherex
2 |
--------------------------------------------------------------------------------
/docs/api_references/patches.md:
--------------------------------------------------------------------------------
1 | :::patcherex2.patches
--------------------------------------------------------------------------------
/docs/core_ideas/patches.md:
--------------------------------------------------------------------------------
1 | ## Patch Types
2 |
3 | The core of Patcherex2 consists of 9 different types of patches, which are used to manipulate the binary in different ways.
4 |
5 | | | Data | Instruction | Function |
6 | |---------:|-------------------|---------------------|---------------------|
7 | | _**Insert**_ | InsertDataPatch | InsertInstructionPatch | InsertFunctionPatch |
8 | | _**Remove**_ | RemoveDataPatch | RemoveInstructionPatch | RemoveFunctionPatch |
9 | | _**Modify**_ | ModifyDataPatch | ModifyInstructionPatch | ModifyFunctionPatch |
10 |
11 | These patches are categorized into three tiers:
12 |
13 | - Data Patches:
14 | Operating at the raw bytes level, data patches are ideal for patching the `.data` section or any other raw data.
15 |
16 | - Instruction Patches:
17 | These patches target the instruction level, enabling modifications to the assembly code of the binary.
18 |
19 | - Function Patches:
20 | At the highest level, function patches manipulate the binary through C code, this level deals with modifications at the function level.
21 |
22 | Each tier features three patch types:
23 |
24 | - Insert Patch: Adds new data, instructions, or functions to the binary.
25 | - Remove Patch: Deletes existing data, instructions, or functions from the binary.
26 | - Modify Patch: Replaces the content of data, instructions, or functions within the binary.
27 |
28 | ### Insert{Data, Instruction, Function}Patch
29 | - Syntax
30 | ```python
31 | Insert*Patch(addr_or_name, content)
32 | ```
33 | - Arguments
34 | - `addr_or_name`: The address or name of the {data, instruction, function} to be inserted.
35 | - When the first argument is an address, patcherex will insert content right before the given address.
36 | - When the first argument is a name, patcherex will automatically find free spaces in the binary and insert the content there, and the `name` provided can be later used for referencing the inserted content.
37 | - `content`: The content to be inserted.
38 | - Content is different for each patch type:
39 | - For `InsertDataPatch`, `content` is a byte string.
40 | - For `InsertInstructionPatch`, `content` is a list of assembly instructions, separated by newlines.
41 | - For `InsertFunctionPatch`, `content` is a C function.
42 |
43 | ### Modify{Data, Instruction, Function}Patch
44 | - Syntax
45 | ```python
46 | Modify*Patch(addr_or_name, content)
47 | ```
48 | - Arguments
49 | - `addr_or_name`: The address or name of the {data, instruction, function} to be modified.
50 | - When the first argument is an address, patcherex will modify the content at the given address.
51 | - When the first argument is a name, patcherex will try to first find the address of the given name/symbol and then modify the content at that address.
52 | - `content`: The new content to replace the existing content.
53 | - Content is different for each patch type:
54 | - For `ModifyDataPatch`, `content` is a byte string.
55 | - For `ModifyInstructionPatch`, `content` is a list of assembly instructions, separated by newlines.
56 | - For `ModifyFunctionPatch`, `content` is a C function.
57 |
58 | ### Remove{Data, Instruction, Function}Patch
59 | - Syntax
60 | ```python
61 | Remove*Patch(addr_or_name, num_bytes: int)
62 | ```
63 | - Arguments
64 | - `addr_or_name`: The address or name of the {data, instruction, function} to be removed.
65 | - When the first argument is an address, patcherex will remove the content at the given address.
66 | - When the first argument is a name, patcherex will try to first find the address of the given name/symbol and then remove the content at that address.
67 | - `num_bytes`: This is optional for `RemoveInstructionPatch` and `RemoveFunctionPatch`, but required for `RemoveDataPatch`, and specifies the number of bytes to be removed.
68 |
69 | ### Referencing previously inserted content.
70 | Examples:
71 |
72 | - This will load effective address of the data `my_data` into the `rsi` register.
73 | ```python
74 | InsertDataPatch("my_data", b"Hello, World!")
75 | InsertInstructionPatch(0xdeadbeef, "lea rsi, [{my_data}]")
76 | ```
77 | - This will replace the content of function `foo` to call function `bar` and return the result.
78 | ```python
79 | InsertFunctionPatch("bar", "int bar() { return 42; }")
80 | ModifyFunctionPatch("foo", "int bar(void); int foo() { return bar(); }")
81 | ```
82 |
--------------------------------------------------------------------------------
/docs/examples/insert_instruction_patch.md:
--------------------------------------------------------------------------------
1 | # InsertInstructionPatch
2 |
3 | We have a simple C program:
4 |
5 | ```c title="examples/insert_instruction_patch/add.c"
6 | --8<-- "examples/insert_instruction_patch/add.c"
7 | ```
8 |
9 | And here is the disassembly of the compiled binary:
10 |
11 | ```asm title="examples/insert_instruction_patch/add"
12 | 0000000000001149 :
13 | 1149: f3 0f 1e fa endbr64
14 | 114d: 55 push %rbp
15 | 114e: 48 89 e5 mov %rsp,%rbp
16 | 1151: 89 7d fc mov %edi,-0x4(%rbp)
17 | 1154: 89 75 f8 mov %esi,-0x8(%rbp)
18 | 1157: 8b 55 fc mov -0x4(%rbp),%edx
19 | 115a: 8b 45 f8 mov -0x8(%rbp),%eax
20 | 115d: 01 d0 add %edx,%eax
21 | 115f: 5d pop %rbp
22 | 1160: c3 ret
23 |
24 | 0000000000001161 :
25 | 1161: f3 0f 1e fa endbr64
26 | 1165: 55 push %rbp
27 | 1166: 48 89 e5 mov %rsp,%rbp
28 | 1169: be 03 00 00 00 mov $0x3,%esi
29 | 116e: bf 02 00 00 00 mov $0x2,%edi
30 | 1173: e8 d1 ff ff ff call 1149
31 | 1178: 89 c6 mov %eax,%esi
32 | 117a: 48 8d 05 83 0e 00 00 lea 0xe83(%rip),%rax # 2004 <_IO_stdin_used+0x4>
33 | 1181: 48 89 c7 mov %rax,%rdi
34 | 1184: b8 00 00 00 00 mov $0x0,%eax
35 | 1189: e8 c2 fe ff ff call 1050
36 | 118e: b8 00 00 00 00 mov $0x0,%eax
37 | 1193: 5d pop %rbp
38 | 1194: c3 ret
39 | ```
40 |
41 | Suppose we want to modify the add function to do some
42 | extra calculations on the first argument, for example doubling it and adding 5, without changing the rest of the
43 | function. We can use Patcherex2's `InsertInstructionPatch`
44 | to insert these instructions at the address `114d` which is
45 | at the beginning of the function. To insert instructions, we
46 | need at least enough space to fit a jump instruction
47 | before the function ends, so we cannot insert them
48 | later on in the function.
49 | Here is how:
50 |
51 | ```python title="examples/insert_instruction_patch/patch.py"
52 | --8<-- "examples/insert_instruction_patch/patch.py"
53 | ```
54 |
55 | Now we can run this script and run the patched binary
56 | to see the result:
57 |
58 | ```bash
59 | $ ./add.patched
60 | 2 + 3 = 12
61 | ```
62 |
63 | We have successfully modified the binary at the
64 | instruction level.
65 |
--------------------------------------------------------------------------------
/docs/examples/modify_function_patch.md:
--------------------------------------------------------------------------------
1 | # ModifyFunctionPatch
2 |
3 | Consider a simple C program:
4 |
5 | ```c title="examples/modify_function_patch/add.c"
6 | --8<-- "examples/modify_function_patch/add.c"
7 | ```
8 |
9 | After compiling and executing this program, the output is:
10 |
11 | ```bash
12 | $ gcc -o add add.c && ./add
13 | 2 + 3 = 5
14 | ```
15 |
16 | Now, we can use Patcherex2 to modify the `add` function to multiply the two arguments instead of adding them.
17 |
18 | ```python title="examples/modify_function_patch/patch.py"
19 | --8<-- "examples/modify_function_patch/patch.py"
20 | ```
21 |
22 | Executing the patched program yields a different result:
23 |
24 | ```bash
25 | $ ./add_patched
26 | 2 + 3 = 6
27 | ```
28 |
29 | 💥 We've successfully modified the binary with Patcherex2!
30 |
--------------------------------------------------------------------------------
/docs/examples/multiple_patches.md:
--------------------------------------------------------------------------------
1 | # Multiple Patches
2 |
3 | Here is a simple example of a vulnerable C program which we will use to show how different patches can be used together:
4 |
5 | ```c title="examples/multiple_patches/getline.c"
6 | --8<-- "examples/multiple_patches/getline.c"
7 | ```
8 |
9 | And here is the disassembly of the relevant functions:
10 |
11 | ```asm title="examples/multiple_patches/getline"
12 | 0000000000001189 :
13 | 1189: f3 0f 1e fa endbr64
14 | 118d: 55 push %rbp
15 | 118e: 48 89 e5 mov %rsp,%rbp
16 | 1191: 48 83 ec 20 sub $0x20,%rsp
17 | 1195: 48 89 7d e8 mov %rdi,-0x18(%rbp)
18 | 1199: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
19 | 11a0: 48 8b 05 69 2e 00 00 mov 0x2e69(%rip),%rax # 4010
20 | 11a7: 48 89 c7 mov %rax,%rdi
21 | 11aa: e8 e1 fe ff ff call 1090
22 | 11af: 88 45 fb mov %al,-0x5(%rbp)
23 | 11b2: 80 7d fb 0a cmpb $0xa,-0x5(%rbp)
24 | 11b6: 74 1b je 11d3
25 | 11b8: 8b 45 fc mov -0x4(%rbp),%eax
26 | 11bb: 8d 50 01 lea 0x1(%rax),%edx
27 | 11be: 89 55 fc mov %edx,-0x4(%rbp)
28 | 11c1: 48 63 d0 movslq %eax,%rdx
29 | 11c4: 48 8b 45 e8 mov -0x18(%rbp),%rax
30 | 11c8: 48 01 c2 add %rax,%rdx
31 | 11cb: 0f b6 45 fb movzbl -0x5(%rbp),%eax
32 | 11cf: 88 02 mov %al,(%rdx)
33 | 11d1: eb cd jmp 11a0
34 | 11d3: 90 nop
35 | 11d4: 8b 45 fc mov -0x4(%rbp),%eax
36 | 11d7: 48 63 d0 movslq %eax,%rdx
37 | 11da: 48 8b 45 e8 mov -0x18(%rbp),%rax
38 | 11de: 48 01 d0 add %rdx,%rax
39 | 11e1: c6 00 00 movb $0x0,(%rax)
40 | 11e4: 8b 45 fc mov -0x4(%rbp),%eax
41 | 11e7: c9 leave
42 | 11e8: c3 ret
43 |
44 | 00000000000011e9 :
45 | 11e9: f3 0f 1e fa endbr64
46 | 11ed: 55 push %rbp
47 | 11ee: 48 89 e5 mov %rsp,%rbp
48 | 11f1: 48 83 ec 20 sub $0x20,%rsp
49 | 11f5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
50 | 11fc: 00 00
51 | 11fe: 48 89 45 f8 mov %rax,-0x8(%rbp)
52 | 1202: 31 c0 xor %eax,%eax
53 | 1204: 48 8d 45 ee lea -0x12(%rbp),%rax
54 | 1208: 48 89 c7 mov %rax,%rdi
55 | 120b: e8 79 ff ff ff call 1189
56 | 1210: 48 8d 45 ee lea -0x12(%rbp),%rax
57 | 1214: 48 89 c7 mov %rax,%rdi
58 | 1217: e8 54 fe ff ff call 1070
59 | 121c: b8 00 00 00 00 mov $0x0,%eax
60 | 1221: 48 8b 55 f8 mov -0x8(%rbp),%rdx
61 | 1225: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx
62 | 122c: 00 00
63 | 122e: 74 05 je 1235
64 | 1230: e8 4b fe ff ff call 1080 <__stack_chk_fail@plt>
65 | 1235: c9 leave
66 | 1236: c3 ret
67 | ```
68 |
69 | The program currently reads a string from standard input and echos it back. We can run it with short strings:
70 | ```bash
71 | $ ./getline
72 | aaaaa
73 | aaaaa
74 | ```
75 | And it works fine. Using a longer string, however will cause it to crash:
76 | ```bash
77 | $ ./getline
78 | aaaaaaaaaaaaaaaaaaa
79 | aaaaaaaaaaaaaaaaaaa
80 | *** stack smashing detected ***: terminated
81 | [1] 194473 IOT instruction (core dumped) ./getline
82 | ```
83 |
84 | We will patch this program to fix the vulnerability, by adding a second argument to the `my_getline` function, and printing a message when the buffer would have overflowed. Here is the script:
85 |
86 | ```python title="examples/multiple_patches/patch.py"
87 | --8<-- "examples/multiple_patches/patch.py"
88 | ```
89 |
90 | This patch adds the buffer size as a second argument to `my_getline`. To do this we insert instructions at the beginning to save the argument, and in the loop body we insert a check to see if the index is out of bounds. When it is, we print a message, which was inserted using the `InsertDataPatch`. We can use the `SAVE_CONTEXT` and `RESTORE_CONTEXT` macros (expanded by Patcherex2) to do operations that could modify data we have in registers, like calling a function. We also insert instructions in `main` to put the buffer size in `esi` before calling the function. Now we can run the script to patch the binary and run the new fixed program:
91 | ```bash
92 | $ ./getline.patched
93 | aaaaaaaaaaaaaaaaaaaaaaa
94 | Ran out of space
95 | aaaaaaaaa
96 | ```
97 |
98 | We have successfully fixed the bug with Patcherex2.
99 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Home"
3 | ---
4 |
5 | ---8<--- 'README.md'
6 |
--------------------------------------------------------------------------------
/examples/insert_instruction_patch/add:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/examples/insert_instruction_patch/add
--------------------------------------------------------------------------------
/examples/insert_instruction_patch/add.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int add(int a, int b) {
4 | return a + b;
5 | }
6 |
7 | int main() {
8 | printf("2 + 3 = %d\n", add(2, 3));
9 | return 0;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/insert_instruction_patch/patch.py:
--------------------------------------------------------------------------------
1 | from patcherex2 import *
2 |
3 | p = Patcherex("add")
4 |
5 |
6 | asm_str = """
7 | add edi, edi
8 | add edi, 5
9 | """
10 |
11 | p.patches.append(InsertInstructionPatch(0x114d,asm_str))
12 | p.apply_patches()
13 |
14 | p.save_binary()
15 |
--------------------------------------------------------------------------------
/examples/insert_instruction_patch_c/add:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/examples/insert_instruction_patch_c/add
--------------------------------------------------------------------------------
/examples/insert_instruction_patch_c/add.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int add(int a, int b) {
4 | return a + b;
5 | }
6 |
7 | int main() {
8 | printf("2 + 3 = %d\n", add(2, 3));
9 | return 0;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/insert_instruction_patch_c/patch.py:
--------------------------------------------------------------------------------
1 | from patcherex2 import *
2 | import logging
3 |
4 | logger = logging.getLogger("patcherex2.patches.instruction_patches")
5 | logger.setLevel(logging.INFO)
6 |
7 | p = Patcherex("add", target_opts={"compiler": "clang19"})
8 |
9 | c_forward_header = """
10 | // This string will be inserted outside the micropatch function. It will be inserted before your code.
11 | // This is how you can define types and function forward declarations used by your C micropatch
12 | #include
13 | """
14 |
15 | # The asm_header is inserted in the main body of the patch before the C code. This header is primarily
16 | # useful for gaining access to the stack pointer, which is a register that is unavailable in our C
17 | # code. In this example we have moved rsp to the r12 register, which is a register that is accessible.
18 | # This means that inside the C code we can access variables on the stack by using the r10 variable.
19 | # There is also an asm_footer
20 | asm_header = "mov r12, rsp"
21 |
22 | # We can access assembly registers directly by using their name, while still using high level C constructs
23 | # as well as intermediate variables. Note that you can use a return statement anywhere in your C micropatch
24 | # to jump back to the next instruction after the micropatch insertion point.
25 | c_str = """
26 | rdi += rdi;
27 | rdi += 5;
28 | // Print out rsp as it was before the patch was started
29 | printf("%p\\n", (void *) r12);
30 | """
31 |
32 | # It is generally a good idea to mark some registers as scratch to give the compiler
33 | # breathing room for allocating registers to use for intermediate variables in your micropatch
34 | # All of the registers that we mark as scratch can be freely clobbered by the compiler
35 | # Note that you can still read from scratch registers stored in the variables. What the scratch
36 | # register denotation will indicate however is that the register can be re-used after the variable
37 | # is no longer live.
38 | c_scratch_regs = [
39 | 'r8', 'r9', 'r10', 'r11', 'r13', 'r14', 'r15'
40 | 'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14', 'xmm15'
41 | ]
42 |
43 | # By default floating point registers will have the 'float' type. We can use c_regs_sort to override
44 | # certain registers so they hold different types. In this example we denote that xmm8 is of type double
45 | c_regs_sort = [('xmm8', 'double')]
46 |
47 | config = InsertInstructionPatch.CConfig(
48 | c_forward_header = c_forward_header,
49 | scratch_regs=c_scratch_regs,
50 | regs_sort=c_regs_sort,
51 | asm_header=asm_header
52 | )
53 |
54 | p.patches.append(InsertInstructionPatch(0x114d, c_str, language="C", c_config=config))
55 | p.apply_patches()
56 |
57 | p.binfmt_tool.save_binary()
58 |
--------------------------------------------------------------------------------
/examples/modify_function_patch/add.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int add(int a, int b) {
4 | return a + b;
5 | }
6 |
7 | int main() {
8 | printf("2 + 3 = %d\n", add(2, 3));
9 | return 0;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/modify_function_patch/patch.py:
--------------------------------------------------------------------------------
1 | from patcherex2 import *
2 |
3 | p = Patcherex("add")
4 |
5 | new_add_func = """
6 | int add(int a, int b) {
7 | return a * b;
8 | }
9 | """
10 |
11 | p.patches.append(ModifyFunctionPatch("add", new_add_func))
12 |
13 | p.apply_patches()
14 | p.save_binary("add_patched")
15 |
--------------------------------------------------------------------------------
/examples/multiple_patches/getline:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/examples/multiple_patches/getline
--------------------------------------------------------------------------------
/examples/multiple_patches/getline.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int my_getline(char* buf) {
4 | int i = 0;
5 | while (1) {
6 | char c = getc(stdin);
7 | if (c == '\n') break;
8 | buf[i++] = c;
9 | }
10 | buf[i] = '\0';
11 | return i;
12 | }
13 |
14 | int main() {
15 |
16 | char buf[10];
17 | my_getline(buf);
18 | puts(buf);
19 |
20 | return 0;
21 | }
--------------------------------------------------------------------------------
/examples/multiple_patches/patch.py:
--------------------------------------------------------------------------------
1 | from patcherex2 import *
2 |
3 | p = Patcherex("getline")
4 |
5 | p.patches.append(InsertInstructionPatch(0x120b,"mov esi, 0xa\n"))
6 | p.patches.append(InsertDataPatch("my_str",b"Ran out of space\0"))
7 |
8 | p.patches.append(InsertInstructionPatch(0x1199,"mov [rbp-0xc],esi"))
9 |
10 | asm_string = """
11 | cmp edx, [rbp-0xc]
12 | jl less
13 | SAVE_CONTEXT
14 | lea rdi, [{my_str}]
15 | call {puts}
16 | RESTORE_CONTEXT
17 | mov rsi, [rbp-0x18]
18 | mov byte ptr [rsi+rax], 0
19 | mov eax, edx
20 | leave
21 | ret
22 | less:
23 | """
24 |
25 | p.patches.append(InsertInstructionPatch(0x11c1,asm_string))
26 |
27 | p.apply_patches()
28 |
29 | p.save_binary()
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Patcherex2 Documentation
2 | repo_url: https://github.com/purseclab/patcherex2
3 | repo_name: purseclab/patcherex2
4 | edit_uri: edit/main/docs/
5 |
6 | theme:
7 | name: "material"
8 | features:
9 | - search.suggest
10 | - content.code.copy
11 | - toc.follow
12 | - content.action.edit
13 | - navigation.tabs
14 | - navigation.tracking
15 | - navigation.path
16 | - navigation.top
17 | palette:
18 | - scheme: pursec
19 |
20 | plugins:
21 | - mkdocstrings:
22 | handlers:
23 | python:
24 | options:
25 | docstring_style: sphinx
26 | show_symbol_type_heading: true
27 | show_symbol_type_toc: true
28 | - search
29 | - awesome-pages
30 |
31 | extra_css:
32 | - _css/extra.css
33 |
34 | markdown_extensions:
35 | - pymdownx.highlight
36 | - pymdownx.inlinehilite
37 | - pymdownx.snippets
38 | - pymdownx.superfences
39 |
--------------------------------------------------------------------------------
/patcherex2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/patcherex2.png
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=64", "setuptools-scm>=8"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "patcherex2"
7 | dynamic = ["version"]
8 | requires-python = ">=3.8"
9 | readme = "README.md"
10 | dependencies = [
11 | "angr",
12 | "pyelftools",
13 | "pypcode",
14 | "lief",
15 | "keystone-engine",
16 | "intelhex",
17 | "requests"
18 | ]
19 |
20 | [project.optional-dependencies]
21 | ghidra = ["pyhidra"]
22 | all = ["patcherex2[ghidra]"]
23 |
24 | [project.urls]
25 | "Homepage" = "https://github.com/purseclab/Patcherex2"
26 | "Source" = "https://github.com/purseclab/Patcherex2"
27 | "Tracker" = "https://github.com/purseclab/Patcherex2/issues"
28 |
29 | [tool.setuptools.packages.find]
30 | where = ["src"]
31 |
32 | [tool.ruff]
33 | exclude = ["examples"]
34 |
35 | [tool.ruff.lint]
36 | extend-select = ["I", "N", "UP", "B"]
37 |
38 | [tool.setuptools_scm]
39 | # empty
40 |
--------------------------------------------------------------------------------
/src/patcherex2/__init__.py:
--------------------------------------------------------------------------------
1 | # ruff: noqa
2 | from importlib import metadata
3 |
4 | from .patcherex import Patcherex
5 | from .patches import *
6 |
7 | __version__ = metadata.version("patcherex2")
8 |
9 | __all__ = ["Patcherex"] + patches.__all__
10 |
--------------------------------------------------------------------------------
/src/patcherex2/components/allocation_managers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/allocation_managers/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/archinfo/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/aarch64.py:
--------------------------------------------------------------------------------
1 | class Aarch64Info:
2 | nop_bytes = b"\x1f\x20\x03\xd5"
3 | nop_size = 4
4 | jmp_asm = "b {dst}"
5 | jmp_size = 4
6 | alignment = 4
7 | bits = 64
8 | is_variable_length_isa = False
9 | instr_size = 4
10 | call_asm = "bl {dst}"
11 | pc_reg_names = ["pc", "ip"]
12 | save_context_asm = """
13 | sub sp, sp, #0x1f0
14 | stp x0, x1, [sp, #0x0]
15 | stp x2, x3, [sp, #0x10]
16 | stp x4, x5, [sp, #0x20]
17 | stp x6, x7, [sp, #0x30]
18 | stp x8, x9, [sp, #0x40]
19 | stp x10, x11, [sp, #0x50]
20 | stp x12, x13, [sp, #0x60]
21 | stp x14, x15, [sp, #0x70]
22 | stp x16, x17, [sp, #0x80]
23 | stp x18, x19, [sp, #0x90]
24 | stp x20, x21, [sp, #0xa0]
25 | stp x22, x23, [sp, #0xb0]
26 | stp x24, x25, [sp, #0xc0]
27 | stp x26, x27, [sp, #0xd0]
28 | stp x28, x29, [sp, #0xe0]
29 | str x30, [sp, #0xf0]
30 | """
31 | restore_context_asm = """
32 | ldp x0, x1, [sp, #0x0]
33 | ldp x2, x3, [sp, #0x10]
34 | ldp x4, x5, [sp, #0x20]
35 | ldp x6, x7, [sp, #0x30]
36 | ldp x8, x9, [sp, #0x40]
37 | ldp x10, x11, [sp, #0x50]
38 | ldp x12, x13, [sp, #0x60]
39 | ldp x14, x15, [sp, #0x70]
40 | ldp x16, x17, [sp, #0x80]
41 | ldp x18, x19, [sp, #0x90]
42 | ldp x20, x21, [sp, #0xa0]
43 | ldp x22, x23, [sp, #0xb0]
44 | ldp x24, x25, [sp, #0xc0]
45 | ldp x26, x27, [sp, #0xd0]
46 | ldp x28, x29, [sp, #0xe0]
47 | ldr x30, [sp, #0xf0]
48 | add sp, sp, #0x1f0
49 | """
50 |
51 | cc = {
52 | "Linux": ["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"],
53 | "LinuxPreserveNone": [
54 | "x20",
55 | "x21",
56 | "x22",
57 | "x23",
58 | "x24",
59 | "x25",
60 | "x26",
61 | "x27",
62 | "x28",
63 | "x0",
64 | "x1",
65 | "x2",
66 | "x3",
67 | "x4",
68 | "x5",
69 | "x6",
70 | "x7",
71 | "x10",
72 | "x11",
73 | "x12",
74 | "x13",
75 | "x14",
76 | "x9",
77 | ],
78 | }
79 | callee_saved = {
80 | "Linux": [
81 | "x19",
82 | "x20",
83 | "x21",
84 | "x22",
85 | "x23",
86 | "x24",
87 | "x25",
88 | "x26",
89 | "x27",
90 | "x28",
91 | "x29",
92 | "x30",
93 | ]
94 | }
95 | cc_float = {"Linux": ["v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"]}
96 | callee_saved_float = {
97 | "Linux": ["v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15"]
98 | }
99 |
100 | float_types = {32: "float", 64: "double", 128: "long double"}
101 |
102 | @property
103 | def regs(self):
104 | return list(self.subregisters.keys())
105 |
106 | @property
107 | def regs_float(self):
108 | return list(self.subregisters_float.keys())
109 |
110 | subregisters = {f"x{i}": {64: [f"x{i}"], 32: [f"w{i}"]} for i in range(0, 30 + 1)}
111 |
112 | subregisters_float = {
113 | f"v{i}": {
114 | 128: [f"v{i}"],
115 | 64: [f"d{i}"],
116 | 32: [f"s{i}"],
117 | 16: [f"h{i}"],
118 | 8: [f"b{i}"],
119 | }
120 | for i in range(0, 30 + 1)
121 | }
122 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/amd64.py:
--------------------------------------------------------------------------------
1 | class Amd64Info:
2 | nop_bytes = b"\x90"
3 | nop_size = 1
4 | jmp_asm = "jmp {dst}"
5 | jmp_size = 6
6 | alignment = 4
7 | bits = 64
8 | is_variable_length_isa = True
9 | instr_size = -1 # variable length
10 | call_asm = "call {dst}"
11 | pc_reg_names = ["rip"]
12 | save_context_asm = """
13 | push rax
14 | push rbx
15 | push rcx
16 | push rdx
17 | push rsi
18 | push rdi
19 | push rbp
20 | push rsp
21 | push r8
22 | push r9
23 | push r10
24 | push r11
25 | push r12
26 | push r13
27 | push r14
28 | push r15
29 | """
30 | restore_context_asm = """
31 | pop r15
32 | pop r14
33 | pop r13
34 | pop r12
35 | pop r11
36 | pop r10
37 | pop r9
38 | pop r8
39 | pop rsp
40 | pop rbp
41 | pop rdi
42 | pop rsi
43 | pop rdx
44 | pop rcx
45 | pop rbx
46 | pop rax
47 | """
48 |
49 | cc = {
50 | "Linux": ["rdi", "rsi", "rdx", "rcx", "r8", "r9"],
51 | "LinuxPreserveNone": [
52 | "r12",
53 | "r13",
54 | "r14",
55 | "r15",
56 | "rdi",
57 | "rsi",
58 | "rdx",
59 | "rcx",
60 | "r8",
61 | "r9",
62 | "r11",
63 | "rax",
64 | ],
65 | "Windows": ["rcx", "rdx", "r8", "r9"],
66 | }
67 | callee_saved = {"Linux": ["r12", "r13", "r14", "r15", "rbx", "rsp", "rbp"]}
68 | cc_float = {
69 | "Linux": ["xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"]
70 | }
71 | callee_saved_float = {"Linux": []}
72 |
73 | float_types = {32: "float", 64: "double", 128: "__float128"}
74 |
75 | @property
76 | def regs(self):
77 | return list(self.subregisters.keys())
78 |
79 | @property
80 | def regs_float(self):
81 | return list(self.subregisters_float.keys())
82 |
83 | subregisters = {
84 | "rax": {
85 | 64: ["rax"],
86 | 32: ["eax"],
87 | 16: ["ax"],
88 | # Note that the order of the children registers is important. Only the 0th
89 | # element of this list (al) is used when determining the calling convention.
90 | # That is, we can only use the following argument 'uint8_t al' in the
91 | # calling convention at the rax position. 'uint8_t ah' is NOT allowed.
92 | 8: ["al", "ah"],
93 | },
94 | "rbx": {64: ["rbx"], 32: ["ebx"], 16: ["bx"], 8: ["bl", "bh"]},
95 | "rcx": {64: ["rcx"], 32: ["ecx"], 16: ["cx"], 8: ["cl", "ch"]},
96 | "rdx": {64: ["rdx"], 32: ["edx"], 16: ["dx"], 8: ["dl", "dh"]},
97 | "rsi": {64: ["rsi"], 32: ["esi"], 16: ["si"], 8: ["sil"]},
98 | "rdi": {64: ["rdi"], 32: ["edi"], 16: ["di"], 8: ["dil"]},
99 | "rbp": {64: ["rbp"], 32: ["ebp"], 16: ["bp"], 8: ["bpl"]},
100 | "rsp": {64: ["rsp"], 32: ["esp"], 16: ["sp"], 8: ["spl"]},
101 | "r8": {64: ["r8"], 32: ["r8d"], 16: ["r8w"], 8: ["r8b"]},
102 | "r9": {64: ["r9"], 32: ["r9d"], 16: ["r9w"], 8: ["r9b"]},
103 | "r10": {64: ["r10"], 32: ["r10d"], 16: ["r10w"], 8: ["r10b"]},
104 | "r11": {64: ["r11"], 32: ["r11d"], 16: ["r11w"], 8: ["r11b"]},
105 | "r12": {64: ["r12"], 32: ["r12d"], 16: ["r12w"], 8: ["r12b"]},
106 | "r13": {64: ["r13"], 32: ["r13d"], 16: ["r13w"], 8: ["r13b"]},
107 | "r14": {64: ["r14"], 32: ["r14d"], 16: ["r14w"], 8: ["r14b"]},
108 | "r15": {64: ["r15"], 32: ["r15d"], 16: ["r15w"], 8: ["r15b"]},
109 | }
110 |
111 | subregisters_float = {f"xmm{i}": {128: [f"xmm{i}"]} for i in range(0, 15 + 1)}
112 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/arm.py:
--------------------------------------------------------------------------------
1 | class ArmInfo:
2 | nop_bytes = b"\x00\xf0\x20\xe3" # TODO: thumb
3 | nop_size = 4
4 | jmp_asm = "b {dst}"
5 | jmp_size = 4
6 | alignment = 4
7 | bits = 32
8 | is_variable_length_isa = False
9 | instr_size = 4 # TODO: thumb 2
10 | call_asm = "bl {dst}"
11 | pc_reg_names = ["pc", "r15"]
12 | save_context_asm = """
13 | push {r0-r11}
14 | """
15 | restore_context_asm = """
16 | pop {r0-r11}
17 | """
18 |
19 | cc = {
20 | "Linux": ["r0", "r1", "r2", "r3"],
21 | # TODO: update LinuxPreserveNone once aarch64 support lands
22 | # in LLVM for preserve_none, currently defaults to Linux
23 | "LinuxPreserveNone": ["r0", "r1", "r2", "r3"],
24 | }
25 | callee_saved = {
26 | "Linux": ["r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r13", "r14", "r15"]
27 | }
28 | cc_float = {"Linux": ["d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"]}
29 | callee_saved_float = {
30 | "Linux": ["d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15"]
31 | }
32 |
33 | float_types = {32: "float", 64: "double"}
34 |
35 | @property
36 | def regs(self):
37 | return list(self.subregisters.keys())
38 |
39 | @property
40 | def regs_float(self):
41 | return list(self.subregisters_float.keys())
42 |
43 | subregisters = {f"r{i}": {32: [f"r{i}"]} for i in range(0, 16)}
44 |
45 | subregisters_float = {}
46 | for i in range(0, 16):
47 | subregisters_float[f"d{i}"] = {64: [f"d{i}"], 32: [f"s{2 * i}"]}
48 | for i in range(16, 32):
49 | subregisters_float[f"d{i}"] = {64: [f"d{i}"]}
50 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/mips.py:
--------------------------------------------------------------------------------
1 | class MipsInfo:
2 | nop_bytes = b"\x00\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "j {dst}"
5 | # NOTE: keystone will always add nop for branch delay slot, so include it in size
6 | jmp_size = 8
7 | alignment = 4
8 | bits = 32
9 | is_variable_length_isa = False
10 | instr_size = 4
11 | call_asm = "jal {dst}"
12 | pc_reg_names = ["pc"]
13 | save_context_asm = """
14 | sub $sp, $sp, -124
15 | sw $ra, 120($sp)
16 | sw $s0, 116($sp)
17 | sw $s1, 112($sp)
18 | sw $s2, 108($sp)
19 | sw $s3, 104($sp)
20 | sw $s4, 100($sp)
21 | sw $s5, 96($sp)
22 | sw $s6, 92($sp)
23 | sw $s7, 88($sp)
24 | sw $s8, 84($sp)
25 | sw $s9, 80($sp)
26 | sw $s10, 76($sp)
27 | sw $s11, 72($sp)
28 | sw $s12, 68($sp)
29 | sw $s13, 64($sp)
30 | sw $s14, 60($sp)
31 | sw $s15, 56($sp)
32 | sw $s16, 52($sp)
33 | sw $s17, 48($sp)
34 | sw $s18, 44($sp)
35 | sw $s19, 40($sp)
36 | sw $s20, 36($sp)
37 | sw $s21, 32($sp)
38 | sw $s22, 28($sp)
39 | sw $s23, 24($sp)
40 | sw $s24, 20($sp)
41 | sw $s25, 16($sp)
42 | sw $s26, 12($sp)
43 | sw $s27, 8($sp)
44 | sw $s28, 4($sp)
45 | sw $s29, 0($sp)
46 | """
47 | restore_context_asm = """
48 | lw $s29, 0($sp)
49 | lw $s28, 4($sp)
50 | lw $s27, 8($sp)
51 | lw $s26, 12($sp)
52 | lw $s25, 16($sp)
53 | lw $s24, 20($sp)
54 | lw $s23, 24($sp)
55 | lw $s22, 28($sp)
56 | lw $s21, 32($sp)
57 | lw $s20, 36($sp)
58 | lw $s19, 40($sp)
59 | lw $s18, 44($sp)
60 | lw $s17, 48($sp)
61 | lw $s16, 52($sp)
62 | lw $s15, 56($sp)
63 | lw $s14, 60($sp)
64 | lw $s13, 64($sp)
65 | lw $s12, 68($sp)
66 | lw $s11, 72($sp)
67 | lw $s10, 76($sp)
68 | lw $s9, 80($sp)
69 | lw $s8, 84($sp)
70 | lw $s7, 88($sp)
71 | lw $s6, 92($sp)
72 | lw $s5, 96($sp)
73 | lw $s4, 100($sp)
74 | lw $s3, 104($sp)
75 | lw $s2, 108($sp)
76 | lw $s1, 112($sp)
77 | lw $s0, 116($sp)
78 | lw $ra, 120($sp)
79 | add $sp, $sp, 124
80 | """
81 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/mips64.py:
--------------------------------------------------------------------------------
1 | class Mips64Info:
2 | nop_bytes = b"\x00\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "j {dst}"
5 | # NOTE: keystone will aldays add nop for branch delay slot, so include it in size
6 | jmp_size = 8
7 | alignment = 4
8 | bits = 64
9 | is_variable_length_isa = False
10 | instr_size = 4
11 | call_asm = "jal {dst}"
12 | pc_reg_names = ["pc"]
13 | save_context_asm = """
14 | sub $sp, $sp, -248
15 | sd $ra, 240($sp)
16 | sd $s0, 232($sp)
17 | sd $s1, 224($sp)
18 | sd $s2, 216($sp)
19 | sd $s3, 208($sp)
20 | sd $s4, 200($sp)
21 | sd $s5, 192($sp)
22 | sd $s6, 184($sp)
23 | sd $s7, 176($sp)
24 | sd $s8, 168($sp)
25 | sd $s9, 160($sp)
26 | sd $s10, 152($sp)
27 | sd $s11, 144($sp)
28 | sd $s12, 136($sp)
29 | sd $s13, 128($sp)
30 | sd $s14, 120($sp)
31 | sd $s15, 112($sp)
32 | sd $s16, 104($sp)
33 | sd $s17, 96($sp)
34 | sd $s18, 88($sp)
35 | sd $s19, 80($sp)
36 | sd $s20, 72($sp)
37 | sd $s21, 64($sp)
38 | sd $s22, 56($sp)
39 | sd $s23, 48($sp)
40 | sd $s24, 40($sp)
41 | sd $s25, 32($sp)
42 | sd $s26, 24($sp)
43 | sd $s27, 16($sp)
44 | sd $s28, 8($sp)
45 | sd $s29, 0($sp)
46 | """
47 | restore_context_asm = """
48 | ld $s29, 0($sp)
49 | ld $s28, 8($sp)
50 | ld $s27, 16($sp)
51 | ld $s26, 24($sp)
52 | ld $s25, 32($sp)
53 | ld $s24, 40($sp)
54 | ld $s23, 48($sp)
55 | ld $s22, 56($sp)
56 | ld $s21, 64($sp)
57 | ld $s20, 72($sp)
58 | ld $s19, 80($sp)
59 | ld $s18, 88($sp)
60 | ld $s17, 96($sp)
61 | ld $s16, 104($sp)
62 | ld $s15, 112($sp)
63 | ld $s14, 120($sp)
64 | ld $s13, 128($sp)
65 | ld $s12, 136($sp)
66 | ld $s11, 144($sp)
67 | ld $s10, 152($sp)
68 | ld $s9, 160($sp)
69 | ld $s8, 168($sp)
70 | ld $s7, 176($sp)
71 | ld $s6, 184($sp)
72 | ld $s5, 192($sp)
73 | ld $s4, 200($sp)
74 | ld $s3, 208($sp)
75 | ld $s2, 216($sp)
76 | ld $s1, 224($sp)
77 | ld $s0, 232($sp)
78 | ld $ra, 240($sp)
79 | add $sp, $sp, 248
80 | """
81 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/ppc.py:
--------------------------------------------------------------------------------
1 | class PpcInfo:
2 | nop_bytes = b"\x60\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "b {dst}"
5 | jmp_size = 4
6 | alignment = 4
7 | bits = 32
8 | is_variable_length_isa = False
9 | instr_size = 4
10 | call_asm = "bl {dst}"
11 | pc_reg_names = []
12 | save_context_asm = """
13 | stwu r1, -0x80(r1)
14 | stmw r3, 0x8(r1)
15 | """
16 | restore_context_asm = """
17 | lmw r3, 0x8(r1)
18 | addi r1, r1, 0x80
19 | """
20 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/ppc64.py:
--------------------------------------------------------------------------------
1 | class Ppc64Info:
2 | nop_bytes = b"\x60\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "b {dst}"
5 | jmp_size = 4
6 | alignment = 4
7 | bits = 64
8 | is_variable_length_isa = False
9 | instr_size = 4
10 | call_asm = "bl {dst}"
11 | pc_reg_names = []
12 | save_context_asm = """
13 | stwu r1, -0x80(r1)
14 | stmw r3, 0x8(r1)
15 | """
16 | restore_context_asm = """
17 | lmw r3, 0x8(r1)
18 | addi r1, r1, 0x80
19 | """
20 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/ppc_vle.py:
--------------------------------------------------------------------------------
1 | class PpcVleInfo:
2 | nop_bytes = b"\x01\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "b {dst}"
5 | jmp_size = 4
6 | alignment = 4
7 | bits = 32
8 | is_variable_length_isa = True
9 | instr_size = -1 # variable length
10 | call_asm = "bl {dst}"
11 | pc_reg_names = []
12 | save_context_asm = "" # TODO
13 | restore_context_asm = "" # TODO
14 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/s390x.py:
--------------------------------------------------------------------------------
1 | class S390xInfo:
2 | nop_bytes = b"\x07\x00"
3 | nop_size = 2
4 | jmp_asm = "jg {dst}"
5 | jmp_size = 6
6 | alignment = 2
7 | bits = 64
8 | is_variable_length_isa = True
9 | instr_size = -1 # variable length
10 | call_asm = "brasl %r14, {dst}"
11 | pc_reg_names = []
12 | save_context_asm = """
13 | stmg %r6,%r15,48(%r15)
14 | aghi %r15,-160
15 | """
16 | restore_context_asm = """
17 | lmg %r6,%r15,208(%r15)
18 | """
19 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/sparc.py:
--------------------------------------------------------------------------------
1 | class SparcInfo:
2 | nop_bytes = b"\x01\x00\x00\x00"
3 | nop_size = 4
4 | jmp_asm = "b {dst}\nnop" # nop due to delay slot
5 | jmp_size = 8
6 | alignment = 4
7 | is_variable_length_isa = False
8 | instr_size = 4
9 | call_asm = "call {dst}"
10 | pc_reg_names = ["pc"]
11 | save_context_asm = "" # TODO
12 | restore_context_asm = "" # TODO
13 |
--------------------------------------------------------------------------------
/src/patcherex2/components/archinfo/x86.py:
--------------------------------------------------------------------------------
1 | class X86Info:
2 | nop_bytes = b"\x90"
3 | nop_size = 1
4 | jmp_asm = "jmp {dst}"
5 | jmp_size = 5
6 | alignment = 4
7 | bits = 32
8 | is_variable_length_isa = True
9 | instr_size = -1 # variable length
10 | call_asm = "call {dst}"
11 | pc_reg_names = ["eip"]
12 | save_context_asm = """
13 | pusha
14 | """
15 | restore_context_asm = """
16 | popa
17 | """
18 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/assemblers/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/assembler.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger(__name__)
4 |
5 |
6 | class Assembler:
7 | def __init__(self, p) -> None:
8 | self.p = p
9 |
10 | def resolve_symbols(self, code: str, symbols=None):
11 | if symbols is None:
12 | symbols = {}
13 | _symbols = {}
14 | _symbols.update(self.p.symbols)
15 | _symbols.update(self.p.binary_analyzer.get_all_symbols())
16 | _symbols.update(symbols)
17 |
18 | for symbol, addr in _symbols.items():
19 | code = code.replace(f"{{{symbol}}}", hex(addr))
20 | return code
21 |
22 | def _assemble(self, code: str, base=0, **kwargs) -> None:
23 | raise NotImplementedError()
24 |
25 | def _pre_assemble_hook(self, code: str, base=0) -> None:
26 | return code
27 |
28 | def assemble(self, code: str, base=0, symbols=None, **kwargs) -> None:
29 | if code == "":
30 | return b""
31 | if symbols is None:
32 | symbols = {}
33 | logger.debug(f"Assembling `{code}` at {hex(base)}")
34 | code = self.resolve_symbols(code, symbols=symbols)
35 | code = self._pre_assemble_hook(code, base=base)
36 |
37 | return self._assemble(code, base=base, **kwargs)
38 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/bcc.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import subprocess
4 | import tempfile
5 |
6 | from ..assets.assets import Assets
7 | from .assembler import Assembler
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | class Bcc(Assembler):
13 | def __init__(self, p) -> None:
14 | super().__init__(p)
15 | self.assets_path = Assets("bcc").path
16 |
17 | def _assemble(self, code: str, base=0, **kwargs) -> bytes:
18 | with tempfile.TemporaryDirectory() as td:
19 | with open(os.path.join(td, "code.s"), "w") as f:
20 | f.write(f".org {hex(base)}\n")
21 | f.write(code)
22 | try:
23 | subprocess.run(
24 | [
25 | os.path.join(self.assets_path, "sparc-gaisler-elf-as"),
26 | "-Aleon",
27 | os.path.join(td, "code.s"),
28 | "-o",
29 | os.path.join(td, "obj.o"),
30 | ],
31 | check=True,
32 | capture_output=True,
33 | )
34 | except subprocess.CalledProcessError as e:
35 | logger.error(e.stderr.decode("utf-8"))
36 | raise e
37 | try:
38 | subprocess.run(
39 | [
40 | os.path.join(self.assets_path, "sparc-gaisler-elf-objcopy"),
41 | "-O",
42 | "binary",
43 | "-j",
44 | ".text",
45 | os.path.join(td, "obj.o"),
46 | os.path.join(td, "obj.bin"),
47 | ],
48 | check=True,
49 | capture_output=True,
50 | )
51 | except subprocess.CalledProcessError as e:
52 | logger.error(e.stderr.decode("utf-8"))
53 | raise e
54 | with open(os.path.join(td, "obj.bin"), "rb") as f:
55 | if base != 0:
56 | f.seek(base)
57 | binary = f.read()
58 | logger.debug(f"Assembled bytes: {bytes(binary).hex()}")
59 | return bytes(binary)
60 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/keystone.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import keystone
4 |
5 | from .assembler import Assembler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class Keystone(Assembler):
11 | def __init__(self, p, arch: int, mode: int) -> None:
12 | super().__init__(p)
13 | self.arch = arch
14 | self.mode = mode
15 | self.ks = keystone.Ks(arch, mode)
16 |
17 | def _assemble(self, code: str, base=0, **kwargs) -> bytes:
18 | try:
19 | binary, _ = self.ks.asm(code, base)
20 | logger.debug(f"Assembled bytes: {bytes(binary).hex()}")
21 | return bytes(binary)
22 | except Exception as e:
23 | raise Exception(
24 | f'Failed to assemble: """\n{code}\n"""\nat base: {hex(base)}'
25 | ) from e
26 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/keystone_arm.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import keystone
4 |
5 | from .assembler import Assembler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class KeystoneArm(Assembler):
11 | def __init__(self, p) -> None:
12 | super().__init__(p)
13 | self.ks_arm = keystone.Ks(
14 | keystone.KS_ARCH_ARM, keystone.KS_MODE_ARM + keystone.KS_MODE_LITTLE_ENDIAN
15 | )
16 | self.ks_thumb = keystone.Ks(
17 | keystone.KS_ARCH_ARM,
18 | keystone.KS_MODE_THUMB + keystone.KS_MODE_LITTLE_ENDIAN,
19 | )
20 |
21 | def _assemble(self, code: str, base=0, is_thumb=False) -> bytes:
22 | try:
23 | ks = self.ks_thumb if is_thumb else self.ks_arm
24 | binary, _ = ks.asm(code, base)
25 | logger.debug(f"Assembled bytes: {bytes(binary).hex()}")
26 | return bytes(binary)
27 | except Exception as e:
28 | raise Exception(
29 | f'Failed to assemble: """\n{code}\n"""\nat base: {hex(base)}'
30 | ) from e
31 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/keystone_sparc.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import re
3 |
4 | import keystone
5 |
6 | from .keystone import Keystone
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | class KeystoneSparc(Keystone):
12 | def _pre_assemble_hook(self, code: str, base=0) -> str:
13 | # Hack for sparc, remove this hack if keystone supports sparc relative addressing correctly
14 | # https://www.gaisler.com/doc/sparcv8.pdf
15 | # call (B.24.), all branches instr (B.21.) jumps to PC + 4ximm, so we need to convert absolute address to relative address
16 | if self.arch != keystone.KS_ARCH_SPARC:
17 | return code
18 | result = ""
19 | lineno = 0
20 | for line in code.splitlines():
21 | line = line.strip()
22 | if (
23 | line.startswith(".")
24 | or line.startswith("#")
25 | or line == ""
26 | or line.endswith(":")
27 | ):
28 | result += line + "\n"
29 | continue
30 | if re.match(r"(call|b|ba) 0x[0-9a-fA-F]+", line):
31 | instr, addr = line.split(" ")
32 | addr = int(addr, 16)
33 | disp = addr - base
34 | imm = disp >> 2
35 | logger.debug(
36 | f"converting {line} to {instr} {hex(imm)} (base: {hex(base)})"
37 | )
38 | if instr == "call":
39 | result += f"{instr} {hex(base + imm - lineno)}\n"
40 | elif instr == "b" or instr == "ba":
41 | result += f"{instr} {hex(addr - (4 * lineno))}\n"
42 | else:
43 | result += line + "\n"
44 | lineno += 1
45 | return result
46 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assemblers/ppc_vle.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import re
4 | import subprocess
5 | import tempfile
6 |
7 | from ..assets.assets import Assets
8 | from .assembler import Assembler
9 |
10 | logger = logging.getLogger(__name__)
11 |
12 |
13 | class PpcVle(Assembler):
14 | def __init__(self, p) -> None:
15 | super().__init__(p)
16 | self.assets_path = Assets("ppc_vle").path
17 |
18 | def _assemble(self, code: str, base=0, **kwargs) -> bytes:
19 | code = re.subn(r"\br(\d+)\b", r"\1", code)[0]
20 |
21 | if base is not None:
22 | # produce a list of {instr_offset: instr} pairs
23 | branch_instrs = {}
24 | instr_count = 0
25 | for line in code.splitlines():
26 | line = line.strip()
27 | if (
28 | line.startswith(".")
29 | or line.startswith("#")
30 | or line == ""
31 | or line.endswith(":")
32 | ):
33 | continue
34 | # if line matches "b 0x*" or "bl 0x*", add it to the branch_instrs dict
35 | if re.match(r"b[a-z]* 0x[0-9a-fA-F]+", line):
36 | branch_instrs[instr_count] = line
37 | instr_count += 1
38 |
39 | for i in range(instr_count):
40 | if i in branch_instrs:
41 | branch_instrs[i] = (
42 | branch_instrs[i].split(" ")[0]
43 | + " "
44 | + hex(int(branch_instrs[i].split(" ")[1], 16) - base - 4 * i)
45 | )
46 |
47 | instr_count = 0
48 | for line_count, line in enumerate(code.splitlines()):
49 | if (
50 | line.startswith(".")
51 | or line.startswith("#")
52 | or line == ""
53 | or line.endswith(":")
54 | ):
55 | continue
56 | if instr_count in branch_instrs:
57 | code = code.splitlines()
58 | code[line_count] = branch_instrs[instr_count]
59 | code = "\n".join(code)
60 | instr_count += 1
61 |
62 | # set base address
63 | if base is not None:
64 | code = f".org {hex(base)}\n" + code
65 |
66 | with tempfile.TemporaryDirectory() as td:
67 | with open(os.path.join(td, "code.s"), "w") as f:
68 | f.write(code)
69 | try:
70 | subprocess.run(
71 | [
72 | os.path.join(self.assets_path, "powerpc-eabivle-as"),
73 | "-o",
74 | os.path.join(td, "obj.o"),
75 | os.path.join(td, "code.s"),
76 | ],
77 | check=True,
78 | capture_output=True,
79 | )
80 | except subprocess.CalledProcessError as e:
81 | logger.error(e.stderr.decode("utf-8"))
82 | raise e
83 | try:
84 | subprocess.run(
85 | [
86 | os.path.join(self.assets_path, "powerpc-eabivle-objcopy"),
87 | "-O",
88 | "binary",
89 | "-j",
90 | ".text",
91 | os.path.join(td, "obj.o"),
92 | os.path.join(td, "obj.bin"),
93 | ],
94 | check=True,
95 | capture_output=True,
96 | )
97 | except subprocess.CalledProcessError as e:
98 | logger.error(e.stderr.decode("utf-8"))
99 | raise e
100 | with open(os.path.join(td, "obj.bin"), "rb") as f:
101 | if base != 0:
102 | f.seek(base)
103 | binary = f.read()
104 | logger.debug(f"Assembled bytes: {bytes(binary).hex()}")
105 | return bytes(binary)
106 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assets/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 | !assets.py
4 |
--------------------------------------------------------------------------------
/src/patcherex2/components/assets/assets.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import tarfile
4 | import tempfile
5 | from pathlib import Path
6 |
7 | import requests
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | class Assets:
13 | ASSETS_DIR = Path(__file__).parent
14 | ASSETS = {
15 | "bcc": {
16 | "url": "https://assets.patcherex.pursec.han.ac/bcc-2.2.4-gcc-linux64.tar.xz",
17 | "path": ASSETS_DIR / "bcc" / "bcc-2.2.4-gcc" / "bin",
18 | },
19 | "ppc_vle": {
20 | "url": "https://assets.patcherex.pursec.han.ac/powerpc-eabivle.tgz",
21 | "path": ASSETS_DIR / "ppc_vle" / "bin",
22 | },
23 | "llvm_recomp": {
24 | "url": "https://assets.patcherex.pursec.han.ac/llvm_recomp.tgz",
25 | "path": ASSETS_DIR / "llvm_recomp",
26 | },
27 | }
28 |
29 | def __init__(self, name: str) -> None:
30 | self.name = name
31 | self.url = self.ASSETS[name]["url"]
32 | self.path = self.ASSETS[name]["path"]
33 | if not os.path.exists(self.ASSETS_DIR / self.name):
34 | logger.info(f"{self.name} not found, downloading...")
35 | self.download()
36 |
37 | def download(self) -> None:
38 | r = requests.get(self.url)
39 | with tempfile.TemporaryDirectory() as td:
40 | with open(os.path.join(td, "asset.tgz"), "wb") as f:
41 | f.write(r.content)
42 | with tarfile.open(os.path.join(td, "asset.tgz")) as tar:
43 | # FIXME: better use filter here but it requires > py3.12. all tarball are manually verified to be safe so it's fine for now
44 | tar.extractall(path=self.ASSETS_DIR / self.name)
45 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binary_analyzers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/binary_analyzers/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/binary_analyzers/binary_analyzer.py:
--------------------------------------------------------------------------------
1 | class BinaryAnalyzer:
2 | pass
3 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binary_analyzers/ghidra.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 | import tempfile
5 |
6 | from .binary_analyzer import BinaryAnalyzer
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | class Ghidra(BinaryAnalyzer):
12 | def __init__(self, binary_path: str, **kwargs):
13 | import pyhidra
14 |
15 | self.temp_proj_dir_ctx = tempfile.TemporaryDirectory()
16 | self.temp_proj_dir = self.temp_proj_dir_ctx.__enter__()
17 |
18 | self.pyhidra_ctx = pyhidra.open_program(binary_path, self.temp_proj_dir)
19 | self.flatapi = self.pyhidra_ctx.__enter__()
20 | self.currentProgram = self.flatapi.getCurrentProgram()
21 |
22 | import ghidra
23 |
24 | self.ghidra = ghidra
25 |
26 | self.bbm = self.ghidra.program.model.block.BasicBlockModel(self.currentProgram)
27 |
28 | def shutdown(self):
29 | self.pyhidra_ctx.__exit__(None, None, None)
30 | self.temp_proj_dir_ctx.__exit__(None, None, None)
31 |
32 | def normalize_addr(self, addr):
33 | addr = addr.getOffset()
34 | if self.currentProgram.getRelocationTable().isRelocatable():
35 | addr -= self.currentProgram.getImageBase().getOffset()
36 | return addr
37 |
38 | def denormalize_addr(self, addr):
39 | if self.currentProgram.getRelocationTable().isRelocatable():
40 | addr += self.currentProgram.getImageBase().getOffset()
41 | return self.flatapi.toAddr(hex(addr))
42 |
43 | def mem_addr_to_file_offset(self, addr: int) -> int:
44 | addr = self.denormalize_addr(addr)
45 | try:
46 | return (
47 | self.currentProgram.getMemory()
48 | .getAddressSourceInfo(addr)
49 | .getFileOffset()
50 | )
51 | except Exception:
52 | raise Exception("Can't get file offset for addr") from None
53 |
54 | def get_basic_block(self, addr: int) -> dict[str, int | list[int]]:
55 | logger.info(f"getting basic block at 0x{addr} with ghidra")
56 | addr = self.denormalize_addr(addr)
57 |
58 | block = self.bbm.getFirstCodeBlockContaining(
59 | addr, self.ghidra.util.task.TaskMonitor.DUMMY
60 | )
61 | if block is None:
62 | raise Exception(f"Cannot find block containing address 0x{addr}")
63 | instrs = []
64 | ii = self.currentProgram.getListing().getInstructions(block, True)
65 | for i in ii:
66 | instrs.append(self.normalize_addr(i.getAddress()))
67 | return {
68 | "start": self.normalize_addr(block.getMinAddress()),
69 | "end": self.normalize_addr(block.getMinAddress()) + block.getNumAddresses(),
70 | "size": block.getNumAddresses(),
71 | "instruction_addrs": instrs,
72 | }
73 |
74 | def get_instr_bytes_at(self, addr: int, num_instr=1):
75 | addr = self.denormalize_addr(addr)
76 | instr = self.currentProgram.getListing().getInstructionContaining(addr)
77 | if instr is None:
78 | return None
79 | b = instr.getBytes()
80 | for _i in range(1, num_instr):
81 | instr = instr.getNext()
82 | b = b"".join([b, instr.getBytes()])
83 | logger.info(
84 | f"got instr bytes of length {len(b)} for {num_instr} instrs at 0x{addr} with ghidra"
85 | )
86 | return b
87 |
88 | def get_unused_funcs(self) -> list[dict[str, int]]:
89 | logger.info("getting unused funcs with ghidra")
90 | fi = self.currentProgram.getListing().getFunctions(True)
91 | unused_funcs = []
92 | for f in fi:
93 | if not f.getSymbol().hasReferences():
94 | b = f.getBody()
95 | unused_funcs.append(
96 | {
97 | "addr": self.normalize_addr(b.getMinAddress()),
98 | "size": b.getNumAddresses(),
99 | }
100 | )
101 | return unused_funcs
102 |
103 | def get_all_symbols(self) -> dict[str, int]:
104 | logger.info("getting all symbols with ghidra")
105 | symbols = {}
106 | # si = self.currentProgram.getSymbolTable().getAllSymbols(True)
107 | # for s in si:
108 | # if not s.isPrimary():
109 | # continue
110 | # symbols[s.getName()] = self.normalize_addr(
111 | # s.getAddress().getOffset())
112 | fi = self.currentProgram.getListing().getFunctions(True)
113 | for f in fi:
114 | if f.getName() in symbols.keys():
115 | continue
116 | symbols[f.getName()] = self.normalize_addr(f.getEntryPoint())
117 | if self.is_thumb(symbols[f.getName()]):
118 | symbols[f.getName()] += 1
119 | return symbols
120 |
121 | def get_function(self, name_or_addr: int | str) -> dict[str, int] | None:
122 | if isinstance(name_or_addr, int):
123 | name_or_addr = self.denormalize_addr(name_or_addr)
124 | func = self.currentProgram.getListing().getFunctionContaining(name_or_addr)
125 | if func is None:
126 | return None
127 | elif isinstance(name_or_addr, str):
128 | funcs = self.currentProgram.getListing().getGlobalFunctions(name_or_addr)
129 | if len(funcs) == 0:
130 | return None
131 | func = funcs[0]
132 | else:
133 | raise Exception("Invalid type for argument")
134 |
135 | b = func.getBody()
136 | return {
137 | "addr": self.normalize_addr(b.getMinAddress()),
138 | "size": b.getNumAddresses(),
139 | }
140 |
141 | def is_thumb(self, addr: int) -> bool:
142 | addr = self.denormalize_addr(addr)
143 | r = self.currentProgram.getRegister("TMode")
144 | if r is None:
145 | return False
146 | v = self.currentProgram.getProgramContext().getRegisterValue(r, addr)
147 | t = v.unsignedValueIgnoreMask.intValue() == 1
148 | logger.info(f"address 0x{addr} {'is' if t else 'is not'} thumb from ghidra")
149 | return t
150 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binary_analyzers/ida.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from headless_ida import HeadlessIdaRemote
4 |
5 | from .binary_analyzer import BinaryAnalyzer
6 |
7 |
8 | class Ida(BinaryAnalyzer):
9 | _DEFAULT_LOAD_BASE = 0x0
10 |
11 | def __init__(self, binary_path: str, **kwargs) -> None:
12 | self.binary_path = binary_path
13 | self.kwargs = kwargs
14 | self.ida_server_host = (
15 | kwargs["ida_server_host"] if "ida_server_host" in kwargs else "localhost"
16 | )
17 | self.ida_server_port = (
18 | kwargs["ida_server_port"] if "ida_server_port" in kwargs else 1337
19 | )
20 | self._headlessida = HeadlessIdaRemote(
21 | self.ida_server_host, self.ida_server_port, self.binary_path
22 | )
23 | ida_libs = [
24 | "idc",
25 | "idautils",
26 | "idaapi",
27 | "ida_funcs",
28 | "ida_xref",
29 | "ida_nalt",
30 | "ida_auto",
31 | "ida_hexrays",
32 | "ida_name",
33 | "ida_expr",
34 | "ida_struct",
35 | "ida_typeinf",
36 | "ida_loader",
37 | "ida_lines",
38 | "ida_segment",
39 | "ida_gdl",
40 | ]
41 | for lib in ida_libs:
42 | setattr(self, lib, self._headlessida.import_module(lib))
43 |
44 | def mem_addr_to_file_offset(self, addr: int) -> int:
45 | return self.ida_loader.get_fileregion_offset(addr)
46 |
47 | def get_basic_block(self, addr: int) -> dict[str, int | list[int]]:
48 | func = self.ida_funcs.get_func(addr)
49 | instr_addrs = list(func.code_items())
50 | assert addr in instr_addrs, "Invalid address"
51 | flowchart = self.ida_gdl.FlowChart(f=func, flags=self.ida_gdl.FC_PREDS)
52 |
53 | for block in flowchart:
54 | if block.start_ea <= addr < block.end_ea:
55 | return {
56 | "start": block.start_ea,
57 | "end": block.end_ea,
58 | "size": block.end_ea - block.start_ea,
59 | "instruction_addrs": [
60 | ea for ea in instr_addrs if block.start_ea <= ea < block.end_ea
61 | ],
62 | }
63 |
64 | def get_instr_bytes_at(self, addr: int):
65 | return self.p.factory.block(addr, num_inst=1).bytes
66 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binfmt_tools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/binfmt_tools/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/binfmt_tools/binary.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import io
4 | import logging
5 |
6 | from .binfmt_tool import BinFmtTool
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | class Binary(BinFmtTool):
12 | def __init__(self, p, binary_path: str) -> None:
13 | super().__init__(p, binary_path)
14 | self._file = open(binary_path, "rb")
15 | self.file_size = self._file.seek(0, io.SEEK_END)
16 | self._file.seek(0)
17 | self.file_updates = []
18 |
19 | def __del__(self) -> None:
20 | self._file.close()
21 |
22 | def _init_memory_analysis(self) -> None:
23 | pass
24 |
25 | def finalize(self) -> None:
26 | pass
27 |
28 | def save_binary(self, filename: str | None = None) -> None:
29 | if filename is None:
30 | filename = f"{self.binary_path}.patched"
31 | with open(filename, "wb") as f:
32 | self._file.seek(0)
33 | f.write(self._file.read())
34 | # apply the updates
35 | for update in self.file_updates:
36 | f.seek(update["offset"])
37 | f.write(update["content"])
38 |
39 | def update_binary_content(self, offset: int, new_content: bytes) -> None:
40 | logger.debug(
41 | f"Updating offset {hex(offset)} with content ({len(new_content)} bytes) {new_content.hex()}"
42 | )
43 | for update in self.file_updates:
44 | if offset >= update["offset"] and offset < update["offset"] + len(
45 | update["content"]
46 | ):
47 | raise ValueError(
48 | f"Cannot update offset {hex(offset)} with content {new_content}, it overlaps with a previous update"
49 | )
50 | self.file_updates.append({"offset": offset, "content": new_content})
51 | if offset + len(new_content) > self.file_size:
52 | self.file_size = offset + len(new_content)
53 |
54 | def get_binary_content(self, offset: int, size: int) -> bytes:
55 | # FIXME: content partially in the file and partially in the updates (check other binfmt tools as well)
56 | # check if it's in the file updates
57 | for update in self.file_updates:
58 | if offset >= update["offset"] and offset + size <= update["offset"] + len(
59 | update["content"]
60 | ):
61 | return update["content"][
62 | offset - update["offset"] : offset - update["offset"] + size
63 | ]
64 | # otherwise read from the file
65 | self._file.seek(offset)
66 | return self._file.read(size)
67 |
68 | def append_to_binary_content(self, new_content: bytes) -> None:
69 | self.file_updates.append({"offset": self.file_size, "content": new_content})
70 | self.file_size += len(new_content)
71 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binfmt_tools/binfmt_tool.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger(__name__)
4 |
5 |
6 | class BinFmtTool:
7 | def __init__(self, p, binary_path: str) -> None:
8 | self.p = p
9 | self.binary_path = binary_path
10 |
11 | def _init_memory_analysis(self) -> None:
12 | raise NotImplementedError()
13 |
14 | def save_binary(self, filename=None) -> None:
15 | raise NotImplementedError()
16 |
17 | def update_binary_content(self, offset: str, new_content: bytes) -> None:
18 | raise NotImplementedError()
19 |
20 | def append_to_binary_content(self, new_content: bytes) -> None:
21 | raise NotImplementedError()
22 |
--------------------------------------------------------------------------------
/src/patcherex2/components/binfmt_tools/ihex.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import io
4 | import logging
5 |
6 | import intelhex
7 |
8 | from .binfmt_tool import BinFmtTool
9 |
10 | logger = logging.getLogger(__name__)
11 |
12 |
13 | class IHex(BinFmtTool):
14 | def __init__(self, p, binary_path: str) -> None:
15 | super().__init__(p, binary_path)
16 | self._file = open(binary_path, "rb")
17 | self._ihex = intelhex.IntelHex(binary_path)
18 | self.file_size = self._ihex.maxaddr() + 1
19 | self.file_updates = []
20 |
21 | def __del__(self) -> None:
22 | self._file.close()
23 |
24 | def _init_memory_analysis(self) -> None:
25 | pass
26 |
27 | def finalize(self) -> None:
28 | pass
29 |
30 | def save_binary(self, filename: str | None = None) -> None:
31 | for update in self.file_updates:
32 | self._ihex.puts(update["offset"], update["content"])
33 | if filename is None:
34 | filename = f"{self.binary_path}.patched"
35 | sio = io.StringIO()
36 | self._ihex.write_hex_file(sio, byte_count=0x20)
37 | final_content = sio.getvalue()
38 | sio.close()
39 | entry_point = ""
40 | final = ""
41 | for line in final_content.splitlines():
42 | if line.startswith(":04000005"):
43 | entry_point = line
44 | elif line == ":00000001FF":
45 | final += entry_point + "\n"
46 | final += line + "\n"
47 | else:
48 | final += line + "\n"
49 |
50 | with open(filename, "w") as f:
51 | f.write(final)
52 |
53 | def update_binary_content(self, offset: int, new_content: bytes) -> None:
54 | logger.debug(
55 | f"Updating offset {hex(offset)} with content ({len(new_content)} bytes) {new_content.hex()}"
56 | )
57 | for update in self.file_updates:
58 | if offset >= update["offset"] and offset < update["offset"] + len(
59 | update["content"]
60 | ):
61 | raise ValueError(
62 | f"Cannot update offset {hex(offset)} with content {new_content}, it overlaps with a previous update"
63 | )
64 | self.file_updates.append({"offset": offset, "content": new_content})
65 | if offset + len(new_content) > self.file_size:
66 | self.file_size = offset + len(new_content)
67 |
68 | def get_binary_content(self, offset: int, size: int) -> bytes:
69 | # check if it's in the file updates
70 | for update in self.file_updates:
71 | if offset >= update["offset"] and offset + size <= update["offset"] + len(
72 | update["content"]
73 | ):
74 | return update["content"][
75 | offset - update["offset"] : offset - update["offset"] + size
76 | ]
77 | # otherwise read from the file
78 | return self._ihex.tobinarray(start=offset, size=size)
79 |
80 | def append_to_binary_content(self, new_content: bytes) -> None:
81 | self.file_updates.append({"offset": self.file_size, "content": new_content})
82 | self.file_size += len(new_content)
83 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/compilers/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/bcc.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from ..assets.assets import Assets
5 | from .compiler import Compiler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class Bcc(Compiler):
11 | def __init__(self, p) -> None:
12 | super().__init__(p)
13 | self.assets_path = Assets("bcc").path
14 | self._compiler = os.path.join(self.assets_path, "sparc-gaisler-elf-gcc")
15 | self._linker = os.path.join(self.assets_path, "sparc-gaisler-elf-ld")
16 | self._compiler_flags = ["-qbsp=gr712rc", "-mcpu=leon3", "-mfix-gr712rc"]
17 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/clang.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 |
5 | from .compiler import Compiler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class Clang(Compiler):
11 | def __init__(
12 | self, p, clang_version=15, compiler_flags: list[str] | None = None
13 | ) -> None:
14 | super().__init__(p)
15 | self.preserve_none = clang_version >= 19
16 | if compiler_flags is None:
17 | compiler_flags = []
18 | self._compiler = f"clang-{clang_version}"
19 | self._linker = f"ld.lld-{clang_version}"
20 | self._compiler_flags = compiler_flags
21 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/clang_arm.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 |
5 | from .compiler import Compiler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class ClangArm(Compiler):
11 | def __init__(
12 | self, p, clang_version=15, compiler_flags: list[str] | None = None
13 | ) -> None:
14 | super().__init__(p)
15 | if compiler_flags is None:
16 | compiler_flags = []
17 | self._compiler = f"clang-{clang_version}"
18 | self._linker = f"ld.lld-{clang_version}"
19 | self._compiler_flags = compiler_flags
20 |
21 | def compile(
22 | self,
23 | code: str,
24 | base=0,
25 | symbols: dict[str, int] | None = None,
26 | extra_compiler_flags: list[str] | None = None,
27 | is_thumb=False,
28 | **kwargs,
29 | ) -> bytes:
30 | if symbols is None:
31 | symbols = {}
32 | if extra_compiler_flags is None:
33 | extra_compiler_flags = []
34 | if is_thumb:
35 | extra_compiler_flags += ["-mthumb"]
36 | else:
37 | extra_compiler_flags += ["-mno-thumb"]
38 | compiled = super().compile(
39 | code,
40 | base=base,
41 | symbols=symbols,
42 | extra_compiler_flags=extra_compiler_flags,
43 | **kwargs,
44 | )
45 |
46 | # FIXME: damn this is too hacky
47 | _symbols = {}
48 | _symbols.update(self.p.symbols)
49 | _symbols.update(self.p.binary_analyzer.get_all_symbols())
50 | _symbols.update(symbols)
51 | symbols = _symbols
52 | disasm = self.p.disassembler.disassemble(compiled, base=base, is_thumb=is_thumb)
53 | reassembled = b""
54 | for instr in disasm:
55 | if (
56 | is_thumb
57 | and instr["mnemonic"] == "bl"
58 | and int(instr["op_str"][1:], 0) in symbols.values()
59 | ):
60 | disasm_str = (
61 | self.p.disassembler.to_asm_string(instr).replace("bl", "blx") + "\n"
62 | )
63 | reassembled += self.p.assembler.assemble(
64 | disasm_str, base=instr["address"], is_thumb=is_thumb
65 | )
66 | elif (
67 | is_thumb
68 | and instr["mnemonic"] == "blx"
69 | and (int(instr["op_str"][1:], 0) + 1) in symbols.values()
70 | ):
71 | disasm_str = (
72 | self.p.disassembler.to_asm_string(instr).replace("blx", "bl") + "\n"
73 | )
74 | reassembled += self.p.assembler.assemble(
75 | disasm_str, base=instr["address"], is_thumb=is_thumb
76 | )
77 | elif (
78 | not is_thumb
79 | and instr["mnemonic"] == "bl"
80 | and (int(instr["op_str"][1:], 0) + 1) in symbols.values()
81 | ):
82 | disasm_str = (
83 | self.p.disassembler.to_asm_string(instr).replace("bl", "blx") + "\n"
84 | )
85 | reassembled += self.p.assembler.assemble(
86 | disasm_str, base=instr["address"], is_thumb=is_thumb
87 | )
88 | elif (
89 | not is_thumb
90 | and instr["mnemonic"] == "blx"
91 | and int(instr["op_str"][1:], 0) in symbols.values()
92 | ):
93 | disasm_str = (
94 | self.p.disassembler.to_asm_string(instr).replace("blx", "bl") + "\n"
95 | )
96 | reassembled += self.p.assembler.assemble(
97 | disasm_str, base=instr["address"], is_thumb=is_thumb
98 | )
99 | else:
100 | reassembled += compiled[
101 | instr["address"] - base : instr["address"] - base + instr["size"]
102 | ]
103 | compiled = reassembled + compiled[len(reassembled) :]
104 | if len(compiled) % 2 != 0:
105 | compiled += b"\x00"
106 | return compiled
107 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/compiler.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 | import os
5 | import subprocess
6 | import tempfile
7 |
8 | import cle
9 | from elftools.elf.elffile import ELFFile
10 |
11 | logger = logging.getLogger(__name__)
12 |
13 |
14 | class Compiler:
15 | def __init__(self, p) -> None:
16 | self.p = p
17 | # preserve_none is a special attribute flag to allow us to control more registers as input to a C function
18 | # This feature is used for a C instruction patch
19 | self.preserve_none = False
20 |
21 | def compile(
22 | self,
23 | code: str,
24 | base=0,
25 | symbols: dict[str, int] | None = None,
26 | extra_compiler_flags: list[str] | None = None,
27 | **kwargs,
28 | ) -> bytes:
29 | if symbols is None:
30 | symbols = {}
31 | if extra_compiler_flags is None:
32 | extra_compiler_flags = []
33 | with tempfile.TemporaryDirectory() as td:
34 | # source file
35 | with open(os.path.join(td, "code.c"), "w") as f:
36 | f.write(code)
37 |
38 | # compile to object file
39 | try:
40 | args = (
41 | [self._compiler]
42 | + self._compiler_flags
43 | + extra_compiler_flags
44 | + [
45 | "-c",
46 | os.path.join(td, "code.c"),
47 | "-o",
48 | os.path.join(td, "obj.o"),
49 | ]
50 | )
51 | subprocess.run(args, check=True, capture_output=True)
52 | except subprocess.CalledProcessError as e:
53 | logger.error(e.stderr.decode("utf-8"))
54 | raise e
55 |
56 | # linker script
57 | _symbols = {}
58 | _symbols.update(self.p.symbols)
59 | _symbols.update(self.p.binary_analyzer.get_all_symbols())
60 | _symbols.update(symbols)
61 |
62 | # TODO: shouldn't put .rodata in .text, but otherwise switch case jump table won't work
63 | # Note that even we don't include .rodata here, cle might still include it if there is
64 | # no gap between .text and .rodata
65 | with open(os.path.join(td, "obj.o"), "rb") as f:
66 | linker_script_rodata_sections = " ".join(
67 | [
68 | f". = ALIGN({section['sh_addralign']}); *({section.name})"
69 | for section in ELFFile(f).iter_sections()
70 | if section.name.startswith(".rodata")
71 | ]
72 | )
73 | linker_script_symbols = "".join(
74 | f"{name} = {hex(addr)};" for name, addr in _symbols.items()
75 | )
76 |
77 | linker_script = f"SECTIONS {{ .patcherex2 : SUBALIGN(0) {{ . = {hex(base)}; *(.text) {linker_script_rodata_sections} {linker_script_symbols} }} }}"
78 | with open(os.path.join(td, "linker.ld"), "w") as f:
79 | f.write(linker_script)
80 |
81 | # link object file
82 | try:
83 | args = [self._linker] + [
84 | "-relocatable",
85 | os.path.join(td, "obj.o"),
86 | "-T",
87 | os.path.join(td, "linker.ld"),
88 | "-o",
89 | os.path.join(td, "obj_linked.o"),
90 | ]
91 | subprocess.run(args, check=True, capture_output=True)
92 | except subprocess.CalledProcessError as e:
93 | logger.error(e.stderr.decode("utf-8"))
94 | raise e
95 |
96 | # extract compiled code
97 | ld = cle.Loader(
98 | os.path.join(td, "obj_linked.o"), main_opts={"base_addr": 0x0}
99 | )
100 |
101 | patcherex2_section = next(
102 | (s for s in ld.main_object.sections if s.name == ".patcherex2"), None
103 | )
104 | compiled_start = ld.all_objects[0].entry + base
105 |
106 | compiled = ld.memory.load(
107 | compiled_start,
108 | patcherex2_section.memsize - compiled_start,
109 | )
110 | return compiled
111 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/llvm_recomp_arm.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 |
5 | from .llvm_recomp import LLVMRecomp
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class LLVMRecompArm(LLVMRecomp):
11 | def compile(
12 | self,
13 | code: str,
14 | base=0,
15 | symbols: dict[str, int] | None = None,
16 | extra_compiler_flags: list[str] | None = None,
17 | is_thumb=False,
18 | **kwargs,
19 | ) -> bytes:
20 | if symbols is None:
21 | symbols = {}
22 | if extra_compiler_flags is None:
23 | extra_compiler_flags = []
24 | if is_thumb:
25 | extra_compiler_flags += ["-mthumb"]
26 | else:
27 | extra_compiler_flags += ["-mno-thumb"]
28 | compiled = super().compile(
29 | code,
30 | base=base,
31 | symbols=symbols,
32 | extra_compiler_flags=extra_compiler_flags,
33 | **kwargs,
34 | )
35 |
36 | # FIXME: damn this is too hacky
37 | _symbols = {}
38 | _symbols.update(self.p.symbols)
39 | _symbols.update(self.p.binary_analyzer.get_all_symbols())
40 | _symbols.update(symbols)
41 | symbols = _symbols
42 | disasm = self.p.disassembler.disassemble(compiled, base=base, is_thumb=is_thumb)
43 | reassembled = b""
44 | for instr in disasm:
45 | if (
46 | is_thumb
47 | and instr["mnemonic"] == "bl"
48 | and int(instr["op_str"][1:], 0) in symbols.values()
49 | ):
50 | disasm_str = (
51 | self.p.disassembler.to_asm_string(instr).replace("bl", "blx") + "\n"
52 | )
53 | reassembled += self.p.assembler.assemble(
54 | disasm_str, base=instr["address"], is_thumb=is_thumb
55 | )
56 | elif (
57 | is_thumb
58 | and instr["mnemonic"] == "blx"
59 | and (int(instr["op_str"][1:], 0) + 1) in symbols.values()
60 | ):
61 | disasm_str = (
62 | self.p.disassembler.to_asm_string(instr).replace("blx", "bl") + "\n"
63 | )
64 | reassembled += self.p.assembler.assemble(
65 | disasm_str, base=instr["address"], is_thumb=is_thumb
66 | )
67 | elif (
68 | not is_thumb
69 | and instr["mnemonic"] == "bl"
70 | and (int(instr["op_str"][1:], 0) + 1) in symbols.values()
71 | ):
72 | disasm_str = (
73 | self.p.disassembler.to_asm_string(instr).replace("bl", "blx") + "\n"
74 | )
75 | reassembled += self.p.assembler.assemble(
76 | disasm_str, base=instr["address"], is_thumb=is_thumb
77 | )
78 | elif (
79 | not is_thumb
80 | and instr["mnemonic"] == "blx"
81 | and int(instr["op_str"][1:], 0) in symbols.values()
82 | ):
83 | disasm_str = (
84 | self.p.disassembler.to_asm_string(instr).replace("blx", "bl") + "\n"
85 | )
86 | reassembled += self.p.assembler.assemble(
87 | disasm_str, base=instr["address"], is_thumb=is_thumb
88 | )
89 | else:
90 | reassembled += compiled[
91 | instr["address"] - base : instr["address"] - base + instr["size"]
92 | ]
93 | compiled = reassembled + compiled[len(reassembled) :]
94 | if len(compiled) % 2 != 0:
95 | compiled += b"\x00"
96 | return compiled
97 |
--------------------------------------------------------------------------------
/src/patcherex2/components/compilers/ppc_vle.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | from ..assets.assets import Assets
5 | from .compiler import Compiler
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class PpcVle(Compiler):
11 | def __init__(self, p) -> None:
12 | super().__init__(p)
13 | self.assets_path = Assets("ppc_vle").path
14 | self._compiler = os.path.join(self.assets_path, "powerpc-eabivle-gcc")
15 | self._linker = os.path.join(self.assets_path, "powerpc-eabivle-ld")
16 | self._compiler_flags = ["-mno-vle"]
17 |
--------------------------------------------------------------------------------
/src/patcherex2/components/disassemblers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/disassemblers/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/components/disassemblers/capstone.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import capstone
4 |
5 | from .disassembler import Disassembler
6 |
7 |
8 | class Capstone(Disassembler):
9 | def __init__(self, arch: int, mode: int) -> None:
10 | self.cs = capstone.Cs(arch, mode)
11 |
12 | def disassemble(self, input: bytes, base=0, **kwargs) -> list[dict[str, int | str]]:
13 | cs_insns = self.cs.disasm(input, base)
14 | result = []
15 | for insn in cs_insns:
16 | result.append(
17 | {
18 | "address": insn.address,
19 | "size": insn.size,
20 | "mnemonic": insn.mnemonic,
21 | "op_str": insn.op_str,
22 | }
23 | )
24 | return result
25 |
--------------------------------------------------------------------------------
/src/patcherex2/components/disassemblers/capstone_arm.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import capstone
4 |
5 | from .disassembler import Disassembler
6 |
7 |
8 | class CapstoneArm(Disassembler):
9 | def __init__(self, p) -> None:
10 | super().__init__(p)
11 | self.cs_arm = capstone.Cs(
12 | capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM + capstone.CS_MODE_LITTLE_ENDIAN
13 | )
14 | self.cs_thumb = capstone.Cs(
15 | capstone.CS_ARCH_ARM,
16 | capstone.CS_MODE_THUMB + capstone.CS_MODE_LITTLE_ENDIAN,
17 | )
18 |
19 | def disassemble(
20 | self, input: bytes, base=0, is_thumb=False, **kwargs
21 | ) -> list[dict[str, int | str]]:
22 | cs = self.cs_thumb if is_thumb else self.cs_arm
23 | cs_insns = cs.disasm(input, base)
24 | result = []
25 | for insn in cs_insns:
26 | result.append(
27 | {
28 | "address": insn.address,
29 | "size": insn.size,
30 | "mnemonic": insn.mnemonic,
31 | "op_str": insn.op_str,
32 | }
33 | )
34 | return result
35 |
--------------------------------------------------------------------------------
/src/patcherex2/components/disassemblers/disassembler.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 |
4 | class Disassembler:
5 | def __init__(self, p) -> None:
6 | self.p = p
7 |
8 | def disassemble(self, input: bytes, base=0, **kwargs) -> None:
9 | raise NotImplementedError()
10 |
11 | def to_asm_string(self, insn: dict[str, int | str]) -> str:
12 | return "{} {}".format(insn["mnemonic"], insn["op_str"])
13 |
--------------------------------------------------------------------------------
/src/patcherex2/components/disassemblers/ppc_vle.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 | import os
5 | import re
6 | import subprocess
7 | import tempfile
8 |
9 | from ..assets.assets import Assets
10 | from .disassembler import Disassembler
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | class PpcVle(Disassembler):
16 | def __init__(self, p) -> None:
17 | self.p = p
18 | self.assets_path = Assets("ppc_vle").path
19 |
20 | def disassemble(self, input: bytes, base=0, **kwargs) -> list[dict[str, str | int]]:
21 | if isinstance(input, str):
22 | input = bytes(map(ord, input))
23 | with tempfile.TemporaryDirectory() as td:
24 | with open(os.path.join(td, "code.bin"), "wb") as f:
25 | f.write(input)
26 |
27 | try:
28 | proc = subprocess.run(
29 | f"{os.path.join(self.assets_path, 'powerpc-eabivle-objdump')} -D -b binary --adjust-vma={hex(base)} -m powerpc:common -EB {os.path.join(td, 'code')}.bin | tail +8",
30 | shell=True,
31 | check=True,
32 | capture_output=True,
33 | )
34 | str_result = proc.stdout.decode("utf-8")
35 | except subprocess.CalledProcessError as e:
36 | logger.error(e.stderr.decode("utf-8"))
37 | raise e
38 |
39 | result = []
40 | for line in str_result.splitlines():
41 | m = re.match(
42 | r"\s+(?P[0-9a-f]+):\s+(?P([0-9a-f]{2}\s)+)\s+(?P.+?)\s+(?P.+?)$",
43 | line,
44 | )
45 | if m:
46 | instr = m.groupdict()
47 | result.append(
48 | {
49 | "address": int(instr["address"], 16),
50 | "size": len(bytes.fromhex(instr["bytes"])),
51 | "mnemonic": re.sub(r"\s+", "", instr["mnemonic"]),
52 | "op_str": re.sub(
53 | r"\s+", "", instr["op_str"].split(";")[0]
54 | ).replace(",", ", "),
55 | }
56 | )
57 | return result
58 |
--------------------------------------------------------------------------------
/src/patcherex2/components/patch_managers/__init__.py:
--------------------------------------------------------------------------------
1 | from .builtin import BuiltIn
2 | from .imp import Imp
3 | from .patch_manager import PatchManager
4 |
5 | __all__ = ["BuiltIn", "Imp", "PatchManager"]
6 |
--------------------------------------------------------------------------------
/src/patcherex2/components/patch_managers/builtin.py:
--------------------------------------------------------------------------------
1 | from .patch_manager import PatchManager
2 |
3 |
4 | class BuiltIn(PatchManager):
5 | pass
6 |
--------------------------------------------------------------------------------
/src/patcherex2/components/patch_managers/imp.py:
--------------------------------------------------------------------------------
1 | from .patch_manager import PatchManager
2 |
3 |
4 | class Imp(PatchManager):
5 | pass
6 |
--------------------------------------------------------------------------------
/src/patcherex2/components/patch_managers/patch_manager.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from patcherex2.patches import Patch
4 |
5 |
6 | class PatchManager:
7 | def __init__(self) -> None:
8 | self.patches = []
9 | self.analyzed = False
10 |
11 | def add_patch(self, patch: Patch) -> None:
12 | self.analyzed = False
13 | self.patches.append(patch)
14 |
15 | def add_patches(self, patches: list[Patch]) -> None:
16 | for patch in patches:
17 | self.add_patch(patch)
18 |
19 | def export_patches(self, filename: str) -> None:
20 | raise NotImplementedError()
21 |
22 | def import_patches(self, filename: str) -> None:
23 | raise NotImplementedError()
24 |
25 | def analyze_patches(self, ignore_conflicts=False) -> None:
26 | raise NotImplementedError()
27 |
28 | def apply_patches(self, best_effort=False) -> None:
29 | raise NotImplementedError()
30 |
--------------------------------------------------------------------------------
/src/patcherex2/components/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/src/patcherex2/components/utils/__init__.py
--------------------------------------------------------------------------------
/src/patcherex2/patcherex.py:
--------------------------------------------------------------------------------
1 | # ruff: noqa: F403, F405
2 | from __future__ import annotations
3 |
4 | import logging
5 |
6 | from .components.binary_analyzers.ghidra import Ghidra
7 | from .patches import *
8 | from .patches import __all__ as all_patches
9 | from .targets import Target
10 |
11 | logging.Logger.manager.loggerDict["patcherex"] = logging.Logger.manager.loggerDict[
12 | "patcherex2"
13 | ]
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class Patcherex:
18 | """
19 | The main class of the library. This is how you are intended to interact with patches.
20 | """
21 |
22 | def __init__(
23 | self,
24 | binary_path: str,
25 | target_cls: type[Target] | None = None,
26 | target_opts: dict[str, str] | None = None,
27 | components_opts: dict[str, dict[str, str]] | None = None,
28 | ) -> None:
29 | """
30 | Constructor.
31 |
32 | :param binary_path: The path of the binary to patch.
33 | :param target_cls: Specified architecture class to use, otherwise it is automatically detected, defaults to None
34 | :param target_opts: Options to specify components for the target, defaults to None
35 | :param components_opts: Options for configuring each component for the target, defaults to None
36 | """
37 | if target_opts is None:
38 | target_opts = {}
39 | if components_opts is None:
40 | components_opts = {}
41 | self.binary_path = binary_path
42 | if target_cls is None:
43 | self.target = Target.detect_target(self, binary_path)
44 | else:
45 | self.target = target_cls(self, binary_path)
46 |
47 | self.symbols = {}
48 | self.sypy_info = {"patcherex_added_functions": []}
49 | self.patches = []
50 |
51 | # Initialize components
52 | components = [
53 | "assembler",
54 | "disassembler",
55 | "compiler",
56 | "binary_analyzer",
57 | "allocation_manager",
58 | "binfmt_tool",
59 | "utils",
60 | "archinfo",
61 | ]
62 | for component in components:
63 | setattr(
64 | self,
65 | component,
66 | self.target.get_component(
67 | component,
68 | target_opts.get(component),
69 | components_opts.get(component),
70 | ),
71 | )
72 |
73 | # Chosen patch order, making sure all are accounted for
74 | self.patch_order = (
75 | ModifyRawBytesPatch,
76 | RemoveDataPatch,
77 | InsertDataPatch,
78 | ModifyDataPatch,
79 | RemoveLabelPatch,
80 | ModifyLabelPatch,
81 | InsertLabelPatch,
82 | RemoveInstructionPatch,
83 | InsertInstructionPatch,
84 | ModifyInstructionPatch,
85 | RemoveFunctionPatch,
86 | InsertFunctionPatch,
87 | ModifyFunctionPatch,
88 | )
89 | assert len(self.patch_order) == len(all_patches)
90 |
91 | def shutdown(self):
92 | """
93 | Shuts down any resources used by Patcherex2.
94 | This needs to be called when using Ghidra as the binary analyzer when done patching.
95 | """
96 | if isinstance(self.binary_analyzer, Ghidra):
97 | self.binary_analyzer.shutdown()
98 |
99 | def apply_patches(self) -> None:
100 | """
101 | Applies all added patches to the binary. Call this when you have added all the patches you want.
102 | """
103 | # TODO: sort patches properly
104 | # self.patches.sort(key=lambda x: self.patch_order.index(type(x)))
105 | self.patches.sort(
106 | key=lambda x: not isinstance(
107 | x, (ModifyDataPatch, InsertDataPatch, RemoveDataPatch)
108 | )
109 | )
110 | logger.debug(f"Applying patches: {self.patches}")
111 | for patch in self.patches:
112 | patch.apply(self)
113 | self.binfmt_tool.finalize()
114 |
115 | def save_binary(self, filename: str = None) -> None:
116 | """
117 | Save the patched binary to a file.
118 |
119 | :param filename: Name of file to save to, defaults to '.patched'
120 | """
121 | self.binfmt_tool.save_binary(filename)
122 |
--------------------------------------------------------------------------------
/src/patcherex2/patches/__init__.py:
--------------------------------------------------------------------------------
1 | from .data_patches import InsertDataPatch, ModifyDataPatch, RemoveDataPatch
2 | from .dummy_patches import InsertLabelPatch, ModifyLabelPatch, RemoveLabelPatch
3 | from .function_patches import (
4 | InsertFunctionPatch,
5 | ModifyFunctionPatch,
6 | RemoveFunctionPatch,
7 | )
8 | from .instruction_patches import (
9 | InsertInstructionPatch,
10 | ModifyInstructionPatch,
11 | RemoveInstructionPatch,
12 | )
13 | from .patch import Patch
14 | from .raw_patches import ModifyRawBytesPatch
15 |
16 | __all__ = [
17 | "ModifyDataPatch",
18 | "InsertDataPatch",
19 | "RemoveDataPatch",
20 | "InsertLabelPatch",
21 | "ModifyLabelPatch",
22 | "RemoveLabelPatch",
23 | "ModifyFunctionPatch",
24 | "InsertFunctionPatch",
25 | "RemoveFunctionPatch",
26 | "ModifyInstructionPatch",
27 | "InsertInstructionPatch",
28 | "RemoveInstructionPatch",
29 | "ModifyRawBytesPatch",
30 | ]
31 |
32 |
33 | # Other Patches
34 | class ModifyEntryPointPatch(Patch):
35 | def __init__(self, addr: int, parent=None) -> None:
36 | self.addr = addr
37 | super().__init__(parent)
38 |
39 |
40 | # Complex Patches
41 | class InsertFunctionWrapperPatch(Patch):
42 | def __init__(self, addr: int, wrapper_code: str, parent=None) -> None:
43 | self.addr = addr
44 | self.wrapper_code = wrapper_code
45 | super().__init__(parent)
46 |
--------------------------------------------------------------------------------
/src/patcherex2/patches/data_patches.py:
--------------------------------------------------------------------------------
1 | """
2 | Contains patches that modify the binary at the data level.
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from typing import TYPE_CHECKING
8 |
9 | from ..components.allocation_managers.allocation_manager import MemoryFlag
10 | from .patch import Patch
11 | from .raw_patches import ModifyRawBytesPatch
12 |
13 | if TYPE_CHECKING:
14 | from ..patcherex import Patcherex
15 |
16 |
17 | class ModifyDataPatch(ModifyRawBytesPatch):
18 | """
19 | Extends ModifyRawBytesPatch to only be used for memory addresses, used for modifying data in binary.
20 | """
21 |
22 | def __init__(self, addr: int, new_bytes: bytes) -> None:
23 | """
24 | Same as ModifyRawBytesPatch constructor, but address type of memory is assumed.
25 | """
26 | super().__init__(addr, new_bytes, addr_type="mem")
27 |
28 |
29 | class InsertDataPatch(Patch):
30 | """
31 | Patch that inserts data into the binary.
32 | """
33 |
34 | def __init__(self, addr_or_name: int | str, data: bytes) -> None:
35 | """
36 | Constructor.
37 |
38 | :param addr_or_name: If an integer, data is inserted at the address.
39 | If a string, it is placed in a free spot in the binary and added as a symbol (with this as its name).
40 | :param data: New data to place in binary.
41 | """
42 | self.addr = None
43 | self.name = None
44 | if isinstance(addr_or_name, int):
45 | self.addr = addr_or_name
46 | elif isinstance(addr_or_name, str):
47 | self.name = addr_or_name
48 | self.data = data
49 |
50 | def apply(self, p: Patcherex) -> None:
51 | """
52 | Applies the patch to the binary, intended to be called by a Patcherex instance.
53 |
54 | :param p: Patcherex instance.
55 | """
56 | if self.addr:
57 | p.binfmt_tool.update_binary_content(self.addr, self.data)
58 | elif self.name:
59 | block = p.allocation_manager.allocate(len(self.data), flag=MemoryFlag.RW)
60 | p.symbols[self.name] = block.mem_addr
61 | p.binfmt_tool.update_binary_content(block.file_addr, self.data)
62 |
63 |
64 | class RemoveDataPatch(ModifyRawBytesPatch):
65 | """
66 | Extends ModifyRawBytesPatch for removing data in the binary (fills it with null bytes starting at address given).
67 | Expects a memory address.
68 | """
69 |
70 | def __init__(self, addr: int, size: int) -> None:
71 | """
72 | Same as ModifyRawBytes Patch constructor, but adds size parameter and assumes memory address.
73 |
74 | :param size: The number of bytes to remove.
75 | """
76 | super().__init__(addr, b"\x00" * size, addr_type="mem")
77 |
--------------------------------------------------------------------------------
/src/patcherex2/patches/dummy_patches.py:
--------------------------------------------------------------------------------
1 | from .patch import Patch
2 |
3 |
4 | class InsertLabelPatch(Patch):
5 | def __init__(self, addr: int) -> None:
6 | self.addr = addr
7 | raise NotImplementedError()
8 |
9 |
10 | class ModifyLabelPatch(Patch):
11 | def __init__(self, addr: int) -> None:
12 | self.addr = addr
13 | raise NotImplementedError()
14 |
15 |
16 | class RemoveLabelPatch(Patch):
17 | def __init__(self, addr: int) -> None:
18 | self.addr = addr
19 | raise NotImplementedError()
20 |
--------------------------------------------------------------------------------
/src/patcherex2/patches/patch.py:
--------------------------------------------------------------------------------
1 | class Patch:
2 | """
3 | Base class for patches. Not instantiated direactly.
4 | """
5 |
6 | def __init__(self, parent=None) -> None:
7 | self.parent = parent
8 |
9 | def apply(self, p):
10 | raise NotImplementedError()
11 |
--------------------------------------------------------------------------------
/src/patcherex2/patches/raw_patches.py:
--------------------------------------------------------------------------------
1 | """
2 | Contains patches that modify the binary at the byte level.
3 | """
4 |
5 | import logging
6 |
7 | from .patch import Patch
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | class ModifyRawBytesPatch(Patch):
13 | """
14 | Patch that modifies bytes of the binary.
15 | """
16 |
17 | def __init__(self, addr: int, new_bytes: bytes, addr_type="mem") -> None:
18 | """
19 | Constructor.
20 |
21 | :param addr: Starting address of bytes you want to change.
22 | :param new_bytes: New bytes to replace original ones.
23 | :param addr_type: The type of address given, "mem" (memory address) or "raw" (file address), defaults to "mem"
24 | """
25 | self.addr = addr
26 | self.new_bytes = new_bytes
27 | self.addr_type = addr_type
28 |
29 | def apply(self, p) -> None:
30 | """
31 | Applies the patch to the binary, intended to be called by a Patcherex instance.
32 |
33 | :param p: Patcherex instance.
34 | :raises NotImplementedError: Raised if an address type other than "raw" or "mem" is specified.
35 | """
36 | if self.addr_type == "raw":
37 | offset = self.addr
38 | elif self.addr_type == "mem":
39 | offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr)
40 | if not offset:
41 | logger.warning(
42 | "failed to convert mem addr to file offset, will just default to raw addr"
43 | )
44 | offset = self.addr
45 | else:
46 | raise NotImplementedError()
47 | p.binfmt_tool.update_binary_content(offset, self.new_bytes)
48 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/__init__.py:
--------------------------------------------------------------------------------
1 | from .bin_arm_bare import BinArmBare
2 | from .elf_aarch64_linux import ElfAArch64Linux
3 | from .elf_amd64_linux import ElfAmd64Linux
4 | from .elf_amd64_linux_recomp import ElfAmd64LinuxRecomp
5 | from .elf_arm_bare import ElfArmBare
6 | from .elf_arm_linux import ElfArmLinux
7 | from .elf_arm_linux_recomp import ElfArmLinuxRecomp
8 | from .elf_arm_mimxrt1052 import ElfArmMimxrt1052
9 | from .elf_leon3_bare import ElfLeon3Bare
10 | from .elf_mips64_linux import ElfMips64Linux
11 | from .elf_mips64el_linux import ElfMips64elLinux
12 | from .elf_mips_linux import ElfMipsLinux
13 | from .elf_mipsel_linux import ElfMipselLinux
14 | from .elf_ppc64_linux import ElfPpc64Linux
15 | from .elf_ppc64le_linux import ElfPpc64leLinux
16 | from .elf_ppc_linux import ElfPpcLinux
17 | from .elf_s390x_linux import ElfS390xLinux
18 | from .elf_x86_linux import ElfX86Linux
19 | from .ihex_ppc_bare import IHexPPCBare
20 | from .target import Target
21 |
22 | __all__ = [
23 | "BinArmBare",
24 | "ElfAArch64Linux",
25 | "ElfAmd64Linux",
26 | "ElfAmd64LinuxRecomp",
27 | "ElfArmBare",
28 | "ElfArmLinux",
29 | "ElfArmLinuxRecomp",
30 | "ElfArmMimxrt1052",
31 | "ElfLeon3Bare",
32 | "ElfMips64Linux",
33 | "ElfMips64elLinux",
34 | "ElfMipsLinux",
35 | "ElfMipselLinux",
36 | "ElfPpc64Linux",
37 | "ElfPpc64leLinux",
38 | "ElfPpcLinux",
39 | "ElfS390xLinux",
40 | "ElfX86Linux",
41 | "IHexPPCBare",
42 | "Target",
43 | ]
44 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/bin_arm_bare.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from ..components.allocation_managers.allocation_manager import AllocationManager
4 | from ..components.archinfo.arm import ArmInfo
5 | from ..components.assemblers.keystone_arm import KeystoneArm
6 | from ..components.binary_analyzers.angr import Angr
7 | from ..components.binfmt_tools.binary import Binary
8 | from ..components.compilers.clang_arm import ClangArm
9 | from ..components.disassemblers.capstone_arm import CapstoneArm
10 | from ..components.utils.utils import Utils
11 | from .target import Target
12 |
13 | logger = logging.getLogger(__name__)
14 |
15 |
16 | class BinArmBare(Target):
17 | @staticmethod
18 | def detect_target(binary_path):
19 | return False
20 |
21 | def get_assembler(self, assembler):
22 | assembler = assembler or "keystone"
23 | if assembler == "keystone":
24 | return KeystoneArm(self.p)
25 | raise NotImplementedError()
26 |
27 | def get_allocation_manager(self, allocation_manager):
28 | allocation_manager = allocation_manager or "default"
29 | if allocation_manager == "default":
30 | return AllocationManager(self.p)
31 | raise NotImplementedError()
32 |
33 | def get_compiler(self, compiler):
34 | compiler = compiler or "clang"
35 | if compiler == "clang":
36 | return ClangArm(self.p, compiler_flags=["-target", "arm-linux-gnueabihf"])
37 | elif compiler == "clang19":
38 | return ClangArm(
39 | self.p,
40 | compiler_flags=["-target", "arm-linux-gnueabihf"],
41 | clang_version=19,
42 | )
43 | raise NotImplementedError()
44 |
45 | def get_disassembler(self, disassembler):
46 | disassembler = disassembler or "capstone"
47 | if disassembler == "capstone":
48 | return CapstoneArm(self.p)
49 | raise NotImplementedError()
50 |
51 | def get_binfmt_tool(self, binfmt_tool):
52 | binfmt_tool = binfmt_tool or "default"
53 | if binfmt_tool == "default":
54 | return Binary(self.p, self.binary_path)
55 | raise NotImplementedError()
56 |
57 | def get_binary_analyzer(self, binary_analyzer):
58 | binary_analyzer = binary_analyzer or "angr"
59 | if binary_analyzer == "angr":
60 | return Angr(
61 | self.binary_path,
62 | angr_kwargs={
63 | "arch": "ARMEL",
64 | "auto_load_libs": False,
65 | },
66 | angr_cfg_kwargs={
67 | "normalize": True,
68 | "data_references": True,
69 | },
70 | )
71 | raise NotImplementedError()
72 |
73 | def get_utils(self, utils):
74 | utils = utils or "default"
75 | if utils == "default":
76 | return Utils(self.p, self.binary_path)
77 | raise NotImplementedError()
78 |
79 | def get_archinfo(self, archinfo):
80 | archinfo = archinfo or "default"
81 | if archinfo == "default":
82 | return ArmInfo()
83 | raise NotImplementedError()
84 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_aarch64_linux.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from ..components.allocation_managers.allocation_manager import AllocationManager
4 | from ..components.archinfo.aarch64 import Aarch64Info
5 | from ..components.assemblers.keystone import Keystone, keystone
6 | from ..components.binary_analyzers.angr import Angr
7 | from ..components.binary_analyzers.ghidra import Ghidra
8 | from ..components.binfmt_tools.elf import ELF
9 | from ..components.compilers.clang import Clang
10 | from ..components.disassemblers.capstone import Capstone, capstone
11 | from ..components.utils.utils import Utils
12 | from .target import Target
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class ElfAArch64Linux(Target):
18 | @staticmethod
19 | def detect_target(binary_path):
20 | with open(binary_path, "rb") as f:
21 | magic = f.read(0x14)
22 | if magic.startswith(b"\x7fELF") and magic.startswith(
23 | b"\xb7\x00", 0x12
24 | ): # EM_AARCH64
25 | return True
26 | return False
27 |
28 | def get_assembler(self, assembler):
29 | assembler = assembler or "keystone"
30 | if assembler == "keystone":
31 | return Keystone(
32 | self.p, keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN
33 | )
34 | raise NotImplementedError()
35 |
36 | def get_allocation_manager(self, allocation_manager):
37 | allocation_manager = allocation_manager or "default"
38 | if allocation_manager == "default":
39 | return AllocationManager(self.p)
40 | raise NotImplementedError()
41 |
42 | def get_compiler(self, compiler):
43 | compiler = compiler or "clang"
44 | if compiler == "clang":
45 | return Clang(self.p, compiler_flags=["-target", "aarch64-linux-gnu"])
46 | elif compiler == "clang19":
47 | return Clang(
48 | self.p,
49 | compiler_flags=["-target", "aarch64-linux-gnu"],
50 | clang_version=19,
51 | )
52 | raise NotImplementedError()
53 |
54 | def get_disassembler(self, disassembler):
55 | disassembler = disassembler or "capstone"
56 | if disassembler == "capstone":
57 | return Capstone(capstone.CS_ARCH_ARM64, capstone.CS_MODE_LITTLE_ENDIAN)
58 | raise NotImplementedError()
59 |
60 | def get_binfmt_tool(self, binfmt_tool):
61 | binfmt_tool = binfmt_tool or "pyelftools"
62 | if binfmt_tool == "pyelftools":
63 | return ELF(self.p, self.binary_path)
64 | raise NotImplementedError()
65 |
66 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
67 | binary_analyzer = binary_analyzer or "angr"
68 | if binary_analyzer == "angr":
69 | return Angr(self.binary_path, **kwargs)
70 | if binary_analyzer == "ghidra":
71 | return Ghidra(self.binary_path, **kwargs)
72 | raise NotImplementedError()
73 |
74 | def get_utils(self, utils):
75 | utils = utils or "default"
76 | if utils == "default":
77 | return Utils(self.p, self.binary_path)
78 | raise NotImplementedError()
79 |
80 | def get_archinfo(self, archinfo):
81 | archinfo = archinfo or "default"
82 | if archinfo == "default":
83 | return Aarch64Info()
84 | raise NotImplementedError()
85 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_amd64_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.amd64 import Amd64Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfAmd64Linux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x3e\x00", 0x12
20 | ): # EM_X86_64
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_X86,
30 | keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_64,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p)
44 | elif compiler == "clang19":
45 | return Clang(self.p, clang_version=19)
46 | raise NotImplementedError()
47 |
48 | def get_disassembler(self, disassembler):
49 | disassembler = disassembler or "capstone"
50 | if disassembler == "capstone":
51 | return Capstone(
52 | capstone.CS_ARCH_X86,
53 | capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_64,
54 | )
55 | raise NotImplementedError()
56 |
57 | def get_binfmt_tool(self, binfmt_tool):
58 | binfmt_tool = binfmt_tool or "pyelftools"
59 | if binfmt_tool == "pyelftools":
60 | return ELF(self.p, self.binary_path)
61 | raise NotImplementedError()
62 |
63 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
64 | binary_analyzer = binary_analyzer or "angr"
65 | if binary_analyzer == "angr":
66 | return Angr(self.binary_path, **kwargs)
67 | if binary_analyzer == "ghidra":
68 | return Ghidra(self.binary_path, **kwargs)
69 | raise NotImplementedError()
70 |
71 | def get_utils(self, utils):
72 | utils = utils or "default"
73 | if utils == "default":
74 | return Utils(self.p, self.binary_path)
75 | raise NotImplementedError()
76 |
77 | def get_archinfo(self, archinfo):
78 | archinfo = archinfo or "default"
79 | if archinfo == "default":
80 | return Amd64Info()
81 | raise NotImplementedError()
82 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_amd64_linux_recomp.py:
--------------------------------------------------------------------------------
1 | from ..components.binary_analyzers.angr import Angr
2 | from ..components.binary_analyzers.ghidra import Ghidra
3 | from ..components.compilers.llvm_recomp import LLVMRecomp
4 | from .elf_amd64_linux import ElfAmd64Linux
5 |
6 |
7 | class ElfAmd64LinuxRecomp(ElfAmd64Linux):
8 | @staticmethod
9 | def detect_target(binary_path):
10 | return False
11 |
12 | def get_compiler(self, compiler):
13 | compiler = compiler or "llvm_recomp"
14 | if compiler == "llvm_recomp":
15 | return LLVMRecomp(self.p)
16 | raise NotImplementedError()
17 |
18 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
19 | binary_analyzer = binary_analyzer or "angr"
20 | if binary_analyzer == "angr":
21 | return Angr(self.binary_path, **kwargs)
22 | if binary_analyzer == "ghidra":
23 | return Ghidra(self.binary_path, **kwargs)
24 | raise NotImplementedError()
25 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_arm_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.arm import ArmInfo
3 | from ..components.assemblers.keystone_arm import KeystoneArm
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang_arm import ClangArm
8 | from ..components.disassemblers.capstone_arm import CapstoneArm
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfArmLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x28\x00", 0x12
20 | ): # EM_ARM
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return KeystoneArm(self.p)
28 | raise NotImplementedError()
29 |
30 | def get_allocation_manager(self, allocation_manager):
31 | allocation_manager = allocation_manager or "default"
32 | if allocation_manager == "default":
33 | return AllocationManager(self.p)
34 | raise NotImplementedError()
35 |
36 | def get_compiler(self, compiler):
37 | compiler = compiler or "clang"
38 | if compiler == "clang":
39 | return ClangArm(self.p, compiler_flags=["-target", "arm-linux-gnueabihf"])
40 | elif compiler == "clang19":
41 | return ClangArm(
42 | self.p,
43 | compiler_flags=["-target", "arm-linux-gnueabihf"],
44 | clang_version=19,
45 | )
46 | raise NotImplementedError()
47 |
48 | def get_disassembler(self, disassembler):
49 | disassembler = disassembler or "capstone"
50 | if disassembler == "capstone":
51 | return CapstoneArm(self.p)
52 | raise NotImplementedError()
53 |
54 | def get_binfmt_tool(self, binfmt_tool):
55 | binfmt_tool = binfmt_tool or "pyelftools"
56 | if binfmt_tool == "pyelftools":
57 | return ELF(self.p, self.binary_path)
58 | raise NotImplementedError()
59 |
60 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
61 | binary_analyzer = binary_analyzer or "angr"
62 | if binary_analyzer == "angr":
63 | return Angr(self.binary_path, **kwargs)
64 | if binary_analyzer == "ghidra":
65 | return Ghidra(self.binary_path, **kwargs)
66 | raise NotImplementedError()
67 |
68 | def get_utils(self, utils):
69 | utils = utils or "default"
70 | if utils == "default":
71 | return Utils(self.p, self.binary_path)
72 | raise NotImplementedError()
73 |
74 | def get_archinfo(self, archinfo):
75 | archinfo = archinfo or "default"
76 | if archinfo == "default":
77 | return ArmInfo()
78 | raise NotImplementedError()
79 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_arm_linux_recomp.py:
--------------------------------------------------------------------------------
1 | from ..components.binary_analyzers.angr import Angr
2 | from ..components.binary_analyzers.ghidra import Ghidra
3 | from ..components.compilers.llvm_recomp_arm import LLVMRecompArm
4 | from .elf_arm_linux import ElfArmLinux
5 |
6 |
7 | class ElfArmLinuxRecomp(ElfArmLinux):
8 | @staticmethod
9 | def detect_target(binary_path):
10 | return False
11 |
12 | def get_compiler(self, compiler):
13 | compiler = compiler or "llvm_recomp"
14 | if compiler == "llvm_recomp":
15 | return LLVMRecompArm(self.p)
16 | raise NotImplementedError()
17 |
18 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
19 | binary_analyzer = binary_analyzer or "angr"
20 | if binary_analyzer == "angr":
21 | return Angr(self.binary_path, **kwargs)
22 | if binary_analyzer == "ghidra":
23 | return Ghidra(self.binary_path, **kwargs)
24 | raise NotImplementedError()
25 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_arm_mimxrt1052.py:
--------------------------------------------------------------------------------
1 | from elftools.construct.lib import Container
2 |
3 | from ..components.allocation_managers.allocation_manager import (
4 | FileBlock,
5 | MemoryBlock,
6 | )
7 | from ..components.binfmt_tools.elf import ELF
8 | from .elf_arm_linux import ElfArmLinux
9 |
10 |
11 | class CustomElf(ELF):
12 | def _init_memory_analysis(self):
13 | """
14 | Information from NXP's MCUXpresso IDE:
15 | Flash is code, RAM4 is where data being loaded to
16 |
17 | * For additional code, we can just put them in the free flash space
18 | * For additional data, we also put them in the free flash space, but
19 | we need to update ResetISR to copy them to RAM4
20 |
21 | * The flasher (LinkServer) seems only care about segment headers, so
22 | we can safely ignore the section headers.
23 |
24 | * The IDE will strip the binary then call the flasher, and strip will
25 | remove the segment we added, so we need to add a fake corresponding
26 | section to make sure the segment is not removed.
27 |
28 | Type | Name | Alias | Location | Size
29 | -------|---------------|-------|------------|----------
30 | Flash | BOARD_FLASH | Flash | 0x60000000 | 0x4000000
31 | RAM | SRAM_DTC | RAM | 0x20000000 | 0x20000
32 | RAM | SRAM_ITC | RAM2 | 0x0 | 0x20000
33 | RAM | SRAM_OC | RAM3 | 0x20200000 | 0x20000
34 | RAM | BOARD_SDRAM | RAM4 | 0x80000000 | 0x1e00000
35 | RAM | NCACHE_REGION | RAM5 | 0x81e00000 | 0x200000
36 | """
37 |
38 | # add free flash space to allocation manager
39 | flash_start = 0x60000000
40 | flash_end = 0x64000000
41 | highest_flash_addr = 0x60000000
42 | highest_file_offset = 0
43 | for segment in self._segments:
44 | seg_start = segment["p_paddr"]
45 | seg_end = segment["p_paddr"] + segment["p_memsz"]
46 | if (
47 | flash_start <= seg_start < flash_end
48 | and flash_start <= seg_end < flash_end
49 | and seg_end > highest_flash_addr
50 | ):
51 | highest_flash_addr = seg_end
52 |
53 | if segment["p_offset"] + segment["p_filesz"] > highest_file_offset:
54 | highest_file_offset = segment["p_offset"] + segment["p_filesz"]
55 |
56 | highest_file_offset = (highest_file_offset + 0xFFFF) & ~0xFFFF
57 | block = FileBlock(highest_file_offset, -1)
58 | self.p.allocation_manager.add_block(block)
59 | block = MemoryBlock(highest_flash_addr, -1)
60 | self.p.allocation_manager.add_block(block)
61 |
62 | return
63 |
64 | def finalize(self):
65 | self.p.allocation_manager.finalize()
66 | if len(self.p.allocation_manager.new_mapped_blocks) == 0:
67 | return
68 |
69 | max_align = max([segment["p_align"] for segment in self._segments] + [0])
70 |
71 | # create new load segment for each new mapped block
72 | for block in self.p.allocation_manager.new_mapped_blocks:
73 | self._segments.append(
74 | Container(
75 | **{
76 | "p_type": "PT_LOAD",
77 | "p_offset": block.file_addr,
78 | "p_filesz": block.size,
79 | "p_vaddr": block.mem_addr,
80 | "p_paddr": block.mem_addr,
81 | "p_memsz": block.size,
82 | "p_flags": block.flag,
83 | "p_align": max_align,
84 | }
85 | )
86 | )
87 |
88 | self._sections.append(
89 | Container(
90 | **{
91 | "sh_name": 0,
92 | "sh_type": "SHT_PROGBITS",
93 | "sh_flags": 2,
94 | "sh_addr": block.mem_addr,
95 | "sh_offset": block.file_addr,
96 | "sh_size": block.size,
97 | "sh_link": 0,
98 | "sh_info": 0,
99 | "sh_addralign": max_align,
100 | "sh_entsize": 0,
101 | }
102 | )
103 | )
104 |
105 | # sort segments by p_offset
106 | self._segments = sorted(self._segments, key=lambda x: x["p_offset"])
107 |
108 | # try to merge load segments if they are adjacent and have the same flags and same alignment
109 | # new size = sum of sizes of the two segments + gap between them
110 | while True:
111 | new_segments = []
112 | i = 0
113 | while i < len(self._segments) - 1:
114 | prev_seg = self._segments[i]
115 | next_seg = self._segments[i + 1]
116 | if (
117 | prev_seg["p_type"] == next_seg["p_type"] == "PT_LOAD"
118 | and prev_seg["p_offset"] + prev_seg["p_filesz"]
119 | == next_seg["p_offset"]
120 | and prev_seg["p_vaddr"] + prev_seg["p_memsz"] == next_seg["p_vaddr"]
121 | and prev_seg["p_flags"] == next_seg["p_flags"]
122 | and prev_seg["p_align"] == next_seg["p_align"]
123 | ):
124 | new_segments.append(
125 | Container(
126 | **{
127 | "p_type": "PT_LOAD",
128 | "p_offset": prev_seg["p_offset"],
129 | "p_filesz": prev_seg["p_filesz"]
130 | + next_seg["p_filesz"]
131 | + (
132 | next_seg["p_offset"]
133 | - (prev_seg["p_offset"] + prev_seg["p_filesz"])
134 | ),
135 | "p_vaddr": prev_seg["p_vaddr"],
136 | "p_paddr": prev_seg["p_paddr"],
137 | "p_memsz": prev_seg["p_memsz"]
138 | + next_seg["p_memsz"]
139 | + (
140 | next_seg["p_vaddr"]
141 | - (prev_seg["p_vaddr"] + prev_seg["p_memsz"])
142 | ),
143 | "p_flags": prev_seg["p_flags"],
144 | "p_align": prev_seg["p_align"],
145 | }
146 | )
147 | )
148 | i += 2
149 | else:
150 | new_segments.append(prev_seg)
151 | i += 1
152 | if i == len(self._segments) - 1:
153 | new_segments.append(self._segments[i])
154 | if new_segments == self._segments:
155 | break
156 | self._segments = new_segments
157 |
158 | # generate new phdr at end of the file and update ehdr
159 | last_seg = sorted(self._segments, key=lambda x: x["p_offset"])[-1]
160 | phdr_start = last_seg["p_offset"] + last_seg["p_filesz"]
161 | new_phdr = b""
162 | for segment in self._segments:
163 | new_phdr += self._elf.structs.Elf_Phdr.build(segment)
164 | self.p.binfmt_tool.update_binary_content(phdr_start, new_phdr)
165 |
166 | ehdr = self._elf.header
167 | ehdr["e_phnum"] = len(self._segments)
168 | ehdr["e_phoff"] = phdr_start
169 |
170 | # generate new shdr at end of the file and update ehdr
171 | shdr_start = phdr_start + len(new_phdr)
172 | new_shdr = b""
173 | for section in self._sections:
174 | new_shdr += self._elf.structs.Elf_Shdr.build(section)
175 | self.p.binfmt_tool.update_binary_content(shdr_start, new_shdr)
176 |
177 | ehdr["e_shnum"] = len(self._sections)
178 | ehdr["e_shoff"] = shdr_start
179 | new_ehdr = self._elf.structs.Elf_Ehdr.build(ehdr)
180 | self.p.binfmt_tool.update_binary_content(0, new_ehdr)
181 |
182 |
183 | class ElfArmMimxrt1052(ElfArmLinux):
184 | @staticmethod
185 | def detect_target(binary_path):
186 | return False
187 |
188 | def get_binfmt_tool(self, binfmt_tool):
189 | binfmt_tool = binfmt_tool or "default"
190 | if binfmt_tool == "default":
191 | return CustomElf(self.p, self.binary_path)
192 | raise NotImplementedError()
193 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_leon3_bare.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from ..components.allocation_managers.allocation_manager import (
4 | AllocationManager,
5 | MappedBlock,
6 | MemoryFlag,
7 | )
8 | from ..components.archinfo.sparc import SparcInfo
9 | from ..components.assemblers.bcc import Bcc as BccAssembler
10 | from ..components.assemblers.keystone_sparc import KeystoneSparc, keystone
11 | from ..components.binary_analyzers.angr import Angr
12 | from ..components.binary_analyzers.ghidra import Ghidra
13 | from ..components.binfmt_tools.elf import ELF
14 | from ..components.compilers.bcc import Bcc as BccCompiler
15 | from ..components.disassemblers.capstone import Capstone, capstone
16 | from ..components.utils.utils import Utils
17 | from .target import Target
18 |
19 | logger = logging.getLogger(__name__)
20 |
21 |
22 | class CustomElf(ELF):
23 | def _init_memory_analysis(self):
24 | # remove all non-RWX segments
25 | self._segments = [s for s in self._segments if s["p_flags"] & 0b111 == 0b111]
26 | block = MappedBlock(
27 | self._segments[0]["p_offset"],
28 | self._segments[0]["p_vaddr"],
29 | self._segments[0]["p_memsz"],
30 | is_free=False,
31 | flag=MemoryFlag.RWX,
32 | )
33 | self.p.allocation_manager.add_block(block)
34 |
35 |
36 | class ElfLeon3Bare(Target):
37 | @staticmethod
38 | def detect_target(binary_path):
39 | return False
40 |
41 | def get_assembler(self, assembler):
42 | assembler = assembler or "keystone"
43 | if assembler == "keystone":
44 | return KeystoneSparc(
45 | self.p,
46 | keystone.KS_ARCH_SPARC,
47 | keystone.KS_MODE_SPARC32 + keystone.KS_MODE_BIG_ENDIAN,
48 | )
49 | elif assembler == "bcc":
50 | return BccAssembler(self.p)
51 | raise NotImplementedError()
52 |
53 | def get_allocation_manager(self, allocation_manager):
54 | allocation_manager = allocation_manager or "default"
55 | if allocation_manager == "default":
56 | return AllocationManager(self.p)
57 | raise NotImplementedError()
58 |
59 | def get_compiler(self, compiler):
60 | compiler = compiler or "bcc"
61 | if compiler == "bcc":
62 | return BccCompiler(self.p)
63 | raise NotImplementedError()
64 |
65 | def get_disassembler(self, disassembler):
66 | disassembler = disassembler or "capstone"
67 | if disassembler == "capstone":
68 | return Capstone(capstone.CS_ARCH_SPARC, capstone.CS_MODE_BIG_ENDIAN)
69 | raise NotImplementedError()
70 |
71 | def get_binfmt_tool(self, binfmt_tool):
72 | binfmt_tool = binfmt_tool or "custom"
73 | if binfmt_tool == "custom":
74 | return CustomElf(self.p, self.binary_path)
75 | raise NotImplementedError()
76 |
77 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
78 | binary_analyzer = binary_analyzer or "angr"
79 | if binary_analyzer == "angr":
80 | return Angr(self.binary_path, **kwargs)
81 | if binary_analyzer == "ghidra":
82 | return Ghidra(self.binary_path, **kwargs)
83 | raise NotImplementedError()
84 |
85 | def get_utils(self, utils):
86 | utils = utils or "default"
87 | if utils == "default":
88 | return Utils(self.p, self.binary_path)
89 | raise NotImplementedError()
90 |
91 | def get_archinfo(self, archinfo):
92 | archinfo = archinfo or "default"
93 | if archinfo == "default":
94 | return SparcInfo()
95 | raise NotImplementedError()
96 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_mips64_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.mips64 import Mips64Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfMips64Linux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF\x02") and magic.startswith(
19 | b"\x00\x08", 0x12
20 | ): # EM_MIPS
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_MIPS,
30 | keystone.KS_MODE_BIG_ENDIAN + keystone.KS_MODE_64,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["--target=mips64-linux-gnuabi64"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | return Capstone(
50 | capstone.CS_ARCH_MIPS,
51 | capstone.CS_MODE_BIG_ENDIAN + capstone.CS_MODE_MIPS64,
52 | )
53 | raise NotImplementedError()
54 |
55 | def get_binfmt_tool(self, binfmt_tool):
56 | binfmt_tool = binfmt_tool or "pyelftools"
57 | if binfmt_tool == "pyelftools":
58 | return ELF(self.p, self.binary_path)
59 | raise NotImplementedError()
60 |
61 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
62 | binary_analyzer = binary_analyzer or "angr"
63 | if binary_analyzer == "angr":
64 | return Angr(self.binary_path, **kwargs)
65 | if binary_analyzer == "ghidra":
66 | return Ghidra(self.binary_path, **kwargs)
67 | raise NotImplementedError()
68 |
69 | def get_utils(self, utils):
70 | utils = utils or "default"
71 | if utils == "default":
72 | return Utils(self.p, self.binary_path)
73 | raise NotImplementedError()
74 |
75 | def get_archinfo(self, archinfo):
76 | archinfo = archinfo or "default"
77 | if archinfo == "default":
78 | return Mips64Info()
79 | raise NotImplementedError()
80 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_mips64el_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.mips64 import Mips64Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfMips64elLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF\x02") and magic.startswith(
19 | b"\x08\x00", 0x12
20 | ): # EM_MIPS
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_MIPS,
30 | keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_64,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["--target=mips64el-linux-gnuabi64"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | return Capstone(
50 | capstone.CS_ARCH_MIPS,
51 | capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_MIPS64,
52 | )
53 | raise NotImplementedError()
54 |
55 | def get_binfmt_tool(self, binfmt_tool):
56 | binfmt_tool = binfmt_tool or "pyelftools"
57 | if binfmt_tool == "pyelftools":
58 | return ELF(self.p, self.binary_path)
59 | raise NotImplementedError()
60 |
61 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
62 | binary_analyzer = binary_analyzer or "angr"
63 | if binary_analyzer == "angr":
64 | return Angr(self.binary_path, **kwargs)
65 | if binary_analyzer == "ghidra":
66 | return Ghidra(self.binary_path, **kwargs)
67 | raise NotImplementedError()
68 |
69 | def get_utils(self, utils):
70 | utils = utils or "default"
71 | if utils == "default":
72 | return Utils(self.p, self.binary_path)
73 | raise NotImplementedError()
74 |
75 | def get_archinfo(self, archinfo):
76 | archinfo = archinfo or "default"
77 | if archinfo == "default":
78 | return Mips64Info()
79 | raise NotImplementedError()
80 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_mips_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.mips import MipsInfo
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfMipsLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF\x01") and magic.startswith(
19 | b"\x00\x08", 0x12
20 | ): # EM_MIPS
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_MIPS,
30 | keystone.KS_MODE_BIG_ENDIAN + keystone.KS_MODE_MIPS32,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["--target=mips-linux-gnu"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | return Capstone(
50 | capstone.CS_ARCH_MIPS,
51 | capstone.CS_MODE_BIG_ENDIAN + capstone.CS_MODE_MIPS32,
52 | )
53 | raise NotImplementedError()
54 |
55 | def get_binfmt_tool(self, binfmt_tool):
56 | binfmt_tool = binfmt_tool or "pyelftools"
57 | if binfmt_tool == "pyelftools":
58 | return ELF(self.p, self.binary_path)
59 | raise NotImplementedError()
60 |
61 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
62 | binary_analyzer = binary_analyzer or "angr"
63 | if binary_analyzer == "angr":
64 | return Angr(self.binary_path, **kwargs)
65 | if binary_analyzer == "ghidra":
66 | return Ghidra(self.binary_path, **kwargs)
67 | raise NotImplementedError()
68 |
69 | def get_utils(self, utils):
70 | utils = utils or "default"
71 | if utils == "default":
72 | return Utils(self.p, self.binary_path)
73 | raise NotImplementedError()
74 |
75 | def get_archinfo(self, archinfo):
76 | archinfo = archinfo or "default"
77 | if archinfo == "default":
78 | return MipsInfo()
79 | raise NotImplementedError()
80 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_mipsel_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.mips import MipsInfo
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfMipselLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF\x01") and magic.startswith(
19 | b"\x08\x00", 0x12
20 | ): # EM_MIPS
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_MIPS,
30 | keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_MIPS32,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["--target=mipsel-linux-gnu"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | return Capstone(
50 | capstone.CS_ARCH_MIPS,
51 | capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_MIPS32,
52 | )
53 | raise NotImplementedError()
54 |
55 | def get_binfmt_tool(self, binfmt_tool):
56 | binfmt_tool = binfmt_tool or "pyelftools"
57 | if binfmt_tool == "pyelftools":
58 | return ELF(self.p, self.binary_path)
59 | raise NotImplementedError()
60 |
61 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
62 | binary_analyzer = binary_analyzer or "angr"
63 | if binary_analyzer == "angr":
64 | return Angr(self.binary_path, **kwargs)
65 | if binary_analyzer == "ghidra":
66 | return Ghidra(self.binary_path, **kwargs)
67 | raise NotImplementedError()
68 |
69 | def get_utils(self, utils):
70 | utils = utils or "default"
71 | if utils == "default":
72 | return Utils(self.p, self.binary_path)
73 | raise NotImplementedError()
74 |
75 | def get_archinfo(self, archinfo):
76 | archinfo = archinfo or "default"
77 | if archinfo == "default":
78 | return MipsInfo()
79 | raise NotImplementedError()
80 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_ppc64_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.ppc64 import Ppc64Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfPpc64Linux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x00\x15", 0x12
20 | ): # EM_PPC64
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_PPC,
30 | keystone.KS_MODE_BIG_ENDIAN + keystone.KS_MODE_PPC64,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["-target", "powerpc64-linux-gnu"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | cs = Capstone(
50 | capstone.CS_ARCH_PPC, capstone.CS_MODE_BIG_ENDIAN + capstone.CS_MODE_64
51 | )
52 | # NOTE: Doing this because keystone expects registers to just be numbers
53 | cs.cs.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME
54 | return cs
55 | raise NotImplementedError()
56 |
57 | def get_binfmt_tool(self, binfmt_tool):
58 | binfmt_tool = binfmt_tool or "pyelftools"
59 | if binfmt_tool == "pyelftools":
60 | return ELF(self.p, self.binary_path)
61 | raise NotImplementedError()
62 |
63 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
64 | binary_analyzer = binary_analyzer or "angr"
65 | if binary_analyzer == "angr":
66 | return Angr(self.binary_path, **kwargs)
67 | if binary_analyzer == "ghidra":
68 | return Ghidra(self.binary_path, **kwargs)
69 | raise NotImplementedError()
70 |
71 | def get_utils(self, utils):
72 | utils = utils or "default"
73 | if utils == "default":
74 | return Utils(self.p, self.binary_path)
75 | raise NotImplementedError()
76 |
77 | def get_archinfo(self, archinfo):
78 | archinfo = archinfo or "default"
79 | if archinfo == "default":
80 | return Ppc64Info()
81 | raise NotImplementedError()
82 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_ppc64le_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.ppc64 import Ppc64Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfPpc64leLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x15\x00", 0x12
20 | ): # EM_PPC64
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_PPC,
30 | keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_PPC64,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["-target", "powerpc64le-linux-gnu"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | cs = Capstone(
50 | capstone.CS_ARCH_PPC,
51 | capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_64,
52 | )
53 | # NOTE: Doing this because keystone expects registers to just be numbers
54 | cs.cs.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME
55 | return cs
56 | raise NotImplementedError()
57 |
58 | def get_binfmt_tool(self, binfmt_tool):
59 | binfmt_tool = binfmt_tool or "pyelftools"
60 | if binfmt_tool == "pyelftools":
61 | return ELF(self.p, self.binary_path)
62 | raise NotImplementedError()
63 |
64 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
65 | binary_analyzer = binary_analyzer or "angr"
66 | if binary_analyzer == "angr":
67 | return Angr(self.binary_path, **kwargs)
68 | if binary_analyzer == "ghidra":
69 | return Ghidra(self.binary_path, **kwargs)
70 | raise NotImplementedError()
71 |
72 | def get_utils(self, utils):
73 | utils = utils or "default"
74 | if utils == "default":
75 | return Utils(self.p, self.binary_path)
76 | raise NotImplementedError()
77 |
78 | def get_archinfo(self, archinfo):
79 | archinfo = archinfo or "default"
80 | if archinfo == "default":
81 | return Ppc64Info()
82 | raise NotImplementedError()
83 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_ppc_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.ppc import PpcInfo
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfPpcLinux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x00\x14", 0x12
20 | ): # EM_PPC
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_PPC,
30 | keystone.KS_MODE_BIG_ENDIAN + keystone.KS_MODE_PPC32,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["-target", "powerpc-linux-gnu"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | cs = Capstone(
50 | capstone.CS_ARCH_PPC, capstone.CS_MODE_BIG_ENDIAN + capstone.CS_MODE_32
51 | )
52 | # NOTE: Doing this because keystone expects registers to just be numbers
53 | cs.cs.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME
54 | return cs
55 | raise NotImplementedError()
56 |
57 | def get_binfmt_tool(self, binfmt_tool):
58 | binfmt_tool = binfmt_tool or "pyelftools"
59 | if binfmt_tool == "pyelftools":
60 | return ELF(self.p, self.binary_path)
61 | raise NotImplementedError()
62 |
63 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
64 | binary_analyzer = binary_analyzer or "angr"
65 | if binary_analyzer == "angr":
66 | return Angr(self.binary_path, **kwargs)
67 | if binary_analyzer == "ghidra":
68 | return Ghidra(self.binary_path, **kwargs)
69 | raise NotImplementedError()
70 |
71 | def get_utils(self, utils):
72 | utils = utils or "default"
73 | if utils == "default":
74 | return Utils(self.p, self.binary_path)
75 | raise NotImplementedError()
76 |
77 | def get_archinfo(self, archinfo):
78 | archinfo = archinfo or "default"
79 | if archinfo == "default":
80 | return PpcInfo()
81 | raise NotImplementedError()
82 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_s390x_linux.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from ..components.allocation_managers.allocation_manager import AllocationManager
4 | from ..components.archinfo.s390x import S390xInfo
5 | from ..components.assemblers.keystone import Keystone, keystone
6 | from ..components.binary_analyzers.angr import Angr
7 | from ..components.binary_analyzers.ghidra import Ghidra
8 | from ..components.binfmt_tools.elf import ELF
9 | from ..components.compilers.clang import Clang
10 | from ..components.disassemblers.capstone import Capstone, capstone
11 | from ..components.utils.utils import Utils
12 | from .target import Target
13 |
14 |
15 | class S390xAssembler(Keystone):
16 | def _assemble(self, code: str, base=0, **kwargs) -> bytes:
17 | if base is not None:
18 | new_code = ""
19 | for line in code.splitlines():
20 | line = line.strip()
21 | rounded_base = base - (base % 0x1000000)
22 | if re.match(r"j 0x[0-9a-fA-F]+", line):
23 | new_code += f"j {hex(int(line.split(' ')[1], 16) - rounded_base)}\n"
24 | else:
25 | new_code += line + "\n"
26 | code = new_code
27 | return super()._assemble(code, base, **kwargs)
28 |
29 |
30 | class ElfS390xLinux(Target):
31 | @staticmethod
32 | def detect_target(binary_path):
33 | with open(binary_path, "rb") as f:
34 | magic = f.read(0x14)
35 | if magic.startswith(b"\x7fELF") and magic.startswith(
36 | b"\x00\x16", 0x12
37 | ): # EM_S390
38 | return True
39 | return False
40 |
41 | def get_assembler(self, assembler):
42 | assembler = assembler or "keystone"
43 | if assembler == "keystone":
44 | return S390xAssembler(
45 | self.p, keystone.KS_ARCH_SYSTEMZ, keystone.KS_MODE_BIG_ENDIAN
46 | )
47 | raise NotImplementedError()
48 |
49 | def get_allocation_manager(self, allocation_manager):
50 | allocation_manager = allocation_manager or "default"
51 | if allocation_manager == "default":
52 | return AllocationManager(self.p)
53 | raise NotImplementedError()
54 |
55 | def get_compiler(self, compiler):
56 | compiler = compiler or "clang"
57 | if compiler == "clang":
58 | # NOTE: There are some issue with ld.lld in older versions of clang, use version >= 17
59 | return Clang(
60 | self.p, compiler_flags=["-target", "s390x-linux-gnu"], clang_version=19
61 | )
62 | raise NotImplementedError()
63 |
64 | def get_disassembler(self, disassembler):
65 | disassembler = disassembler or "capstone"
66 | if disassembler == "capstone":
67 | return Capstone(capstone.CS_ARCH_SYSZ, capstone.CS_MODE_BIG_ENDIAN)
68 | raise NotImplementedError()
69 |
70 | def get_binfmt_tool(self, binfmt_tool):
71 | binfmt_tool = binfmt_tool or "pyelftools"
72 | if binfmt_tool == "pyelftools":
73 | return ELF(self.p, self.binary_path)
74 | raise NotImplementedError()
75 |
76 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
77 | binary_analyzer = binary_analyzer or "angr"
78 | if binary_analyzer == "angr":
79 | return Angr(self.binary_path, **kwargs)
80 | if binary_analyzer == "ghidra":
81 | return Ghidra(self.binary_path, **kwargs)
82 | raise NotImplementedError()
83 |
84 | def get_utils(self, utils):
85 | utils = utils or "default"
86 | if utils == "default":
87 | return Utils(self.p, self.binary_path)
88 | raise NotImplementedError()
89 |
90 | def get_archinfo(self, archinfo):
91 | archinfo = archinfo or "default"
92 | if archinfo == "default":
93 | return S390xInfo()
94 | raise NotImplementedError()
95 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/elf_x86_linux.py:
--------------------------------------------------------------------------------
1 | from ..components.allocation_managers.allocation_manager import AllocationManager
2 | from ..components.archinfo.x86 import X86Info
3 | from ..components.assemblers.keystone import Keystone, keystone
4 | from ..components.binary_analyzers.angr import Angr
5 | from ..components.binary_analyzers.ghidra import Ghidra
6 | from ..components.binfmt_tools.elf import ELF
7 | from ..components.compilers.clang import Clang
8 | from ..components.disassemblers.capstone import Capstone, capstone
9 | from ..components.utils.utils import Utils
10 | from .target import Target
11 |
12 |
13 | class ElfX86Linux(Target):
14 | @staticmethod
15 | def detect_target(binary_path):
16 | with open(binary_path, "rb") as f:
17 | magic = f.read(0x14)
18 | if magic.startswith(b"\x7fELF") and magic.startswith(
19 | b"\x03\x00", 0x12
20 | ): # EM_386
21 | return True
22 | return False
23 |
24 | def get_assembler(self, assembler):
25 | assembler = assembler or "keystone"
26 | if assembler == "keystone":
27 | return Keystone(
28 | self.p,
29 | keystone.KS_ARCH_X86,
30 | keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_32,
31 | )
32 | raise NotImplementedError()
33 |
34 | def get_allocation_manager(self, allocation_manager):
35 | allocation_manager = allocation_manager or "default"
36 | if allocation_manager == "default":
37 | return AllocationManager(self.p)
38 | raise NotImplementedError()
39 |
40 | def get_compiler(self, compiler):
41 | compiler = compiler or "clang"
42 | if compiler == "clang":
43 | return Clang(self.p, compiler_flags=["-m32"])
44 | raise NotImplementedError()
45 |
46 | def get_disassembler(self, disassembler):
47 | disassembler = disassembler or "capstone"
48 | if disassembler == "capstone":
49 | return Capstone(
50 | capstone.CS_ARCH_X86,
51 | capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_32,
52 | )
53 | raise NotImplementedError()
54 |
55 | def get_binfmt_tool(self, binfmt_tool):
56 | binfmt_tool = binfmt_tool or "pyelftools"
57 | if binfmt_tool == "pyelftools":
58 | return ELF(self.p, self.binary_path)
59 | raise NotImplementedError()
60 |
61 | def get_binary_analyzer(self, binary_analyzer, **kwargs):
62 | binary_analyzer = binary_analyzer or "angr"
63 | if binary_analyzer == "angr":
64 | return Angr(self.binary_path, **kwargs)
65 | if binary_analyzer == "ghidra":
66 | return Ghidra(self.binary_path, **kwargs)
67 | raise NotImplementedError()
68 |
69 | def get_utils(self, utils):
70 | utils = utils or "default"
71 | if utils == "default":
72 | return Utils(self.p, self.binary_path)
73 | raise NotImplementedError()
74 |
75 | def get_archinfo(self, archinfo):
76 | archinfo = archinfo or "default"
77 | if archinfo == "default":
78 | return X86Info()
79 | raise NotImplementedError()
80 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/ihex_ppc_bare.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import archinfo
4 |
5 | from ..components.allocation_managers.allocation_manager import AllocationManager
6 | from ..components.archinfo.ppc_vle import PpcVleInfo
7 | from ..components.assemblers.ppc_vle import PpcVle as PpcVleAssembler
8 | from ..components.binary_analyzers.angr import Angr
9 | from ..components.binfmt_tools.ihex import IHex
10 | from ..components.compilers.ppc_vle import PpcVle as PpcVleCompiler
11 | from ..components.disassemblers.ppc_vle import PpcVle as PpcVleDisassembler
12 | from ..components.utils.utils import Utils
13 | from .target import Target
14 |
15 | logger = logging.getLogger(__name__)
16 |
17 |
18 | class IHexPPCBare(Target):
19 | @staticmethod
20 | def detect_target(binary_path):
21 | return False
22 |
23 | def get_assembler(self, assembler):
24 | assembler = assembler or "default"
25 | if assembler == "default":
26 | return PpcVleAssembler(self.p)
27 | raise NotImplementedError()
28 |
29 | def get_allocation_manager(self, allocation_manager):
30 | allocation_manager = allocation_manager or "default"
31 | if allocation_manager == "default":
32 | return AllocationManager(self.p)
33 | raise NotImplementedError()
34 |
35 | def get_compiler(self, compiler):
36 | compiler = compiler or "default"
37 | if compiler == "default":
38 | return PpcVleCompiler(self.p)
39 | raise NotImplementedError()
40 |
41 | def get_disassembler(self, disassembler):
42 | disassembler = disassembler or "default"
43 | if disassembler == "default":
44 | return PpcVleDisassembler(self.p)
45 | raise NotImplementedError()
46 |
47 | def get_binfmt_tool(self, binfmt_tool):
48 | binfmt_tool = binfmt_tool or "default"
49 | if binfmt_tool == "default":
50 | return IHex(self.p, self.binary_path)
51 | raise NotImplementedError()
52 |
53 | def get_binary_analyzer(self, binary_analyzer):
54 | binary_analyzer = binary_analyzer or "angr"
55 | if binary_analyzer == "angr":
56 | return Angr(
57 | self.binary_path,
58 | angr_kwargs={
59 | "arch": archinfo.ArchPcode("PowerPC:BE:32:MPC8270"),
60 | "auto_load_libs": False,
61 | "load_debug_info": True,
62 | },
63 | angr_cfg_kwargs={
64 | "normalize": True,
65 | "data_references": True,
66 | "force_smart_scan": False,
67 | "force_complete_scan": False,
68 | },
69 | )
70 | raise NotImplementedError()
71 |
72 | def get_utils(self, utils):
73 | utils = utils or "default"
74 | if utils == "default":
75 | return Utils(self.p, self.binary_path)
76 | raise NotImplementedError()
77 |
78 | def get_archinfo(self, archinfo):
79 | archinfo = archinfo or "default"
80 | if archinfo == "default":
81 | return PpcVleInfo()
82 | raise NotImplementedError()
83 |
--------------------------------------------------------------------------------
/src/patcherex2/targets/target.py:
--------------------------------------------------------------------------------
1 | class Target:
2 | target_classes = []
3 |
4 | def __init__(self, p, binary_path):
5 | self.binary_path = binary_path
6 | self.p = p
7 |
8 | def __init_subclass__(cls, **kwargs):
9 | super().__init_subclass__(**kwargs)
10 | cls.target_classes.append(cls)
11 |
12 | @classmethod
13 | def detect_target(cls, p, binary_path):
14 | for target_class in cls.target_classes:
15 | if target_class.detect_target(binary_path):
16 | return target_class(p, binary_path)
17 | raise ValueError("Unknown target")
18 |
19 | def get_component(self, component_type, component_name, component_opts=None):
20 | if component_opts is None:
21 | component_opts = {}
22 | return getattr(self, f"get_{component_type}")(component_name, **component_opts)
23 |
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/iip_c
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | uint32_t add_2_then_double(uint32_t n) {
5 | uint32_t ret = n + 2;
6 | ret *= 2;
7 | return ret;
8 | }
9 |
10 | int main(int argc, char *argv[]) {
11 | uint32_t num = add_2_then_double(4);
12 | printf("%d", num);
13 | return 0;
14 | }
15 |
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c_asm_header:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/iip_c_asm_header
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c_asm_header.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | void print_areas(float *radii, float *areas, int num_circles) {
4 | for (int i = 0; i < num_circles; i++) {
5 | printf("The area of the circle with radius %f is %f\n", radii[i], areas[i]);
6 | }
7 | }
8 |
9 | void compute_areas(float *radii, int num_radii) {
10 | float areas[num_radii];
11 | for (int i = 0; i < num_radii; i++) {
12 | // Error is here! We should have multiplied by another radius, but we forgot :(
13 | areas[i] = 3.14 * radii[i];
14 | }
15 | print_areas(radii, areas, num_radii);
16 | }
17 |
18 | void main() {
19 | float radii[3] = { 1.5, 2.0, 4.3 };
20 | compute_areas(radii, 3);
21 | }
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c_float:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/iip_c_float
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/iip_c_float.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | float magnitude2(float x, float y, float z) {
4 | return x * x + y * y;
5 | }
6 |
7 | void test(float x, float y, float z) {
8 | float mag2 = magnitude2(x, y, z);
9 | printf("The square magnitude of the vector (%f, %f, %f) is %f\n", x, y, z, mag2);
10 | }
11 |
12 | void main() {
13 | test(0.0f, 0.0f, 0.0f);
14 | test(1.0f, 2.0f, 3.0f);
15 | test(-20.0f, 33.2f, 5.2f);
16 | test(3.0f, 4.0f, 0.0f);
17 | }
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/aarch64/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/aarch64/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/iip_c
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | uint32_t add_2_then_double(uint32_t n) {
5 | uint32_t ret = n + 2;
6 | ret *= 2;
7 | return ret;
8 | }
9 |
10 | int main(int argc, char *argv[]) {
11 | uint32_t num = add_2_then_double(4);
12 | printf("%d", num);
13 | return 0;
14 | }
15 |
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c_asm_header:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/iip_c_asm_header
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c_asm_header.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | void print_areas(float *radii, float *areas, int num_circles) {
4 | for (int i = 0; i < num_circles; i++) {
5 | printf("The area of the circle with radius %f is %f\n", radii[i], areas[i]);
6 | }
7 | }
8 |
9 | void compute_areas(float *radii, int num_radii) {
10 | float areas[num_radii];
11 | for (int i = 0; i < num_radii; i++) {
12 | // Error is here! We should have multiplied by another radius, but we forgot :(
13 | areas[i] = 3.14 * radii[i];
14 | }
15 | print_areas(radii, areas, num_radii);
16 | }
17 |
18 | void main() {
19 | float radii[3] = { 1.5, 2.0, 4.3 };
20 | compute_areas(radii, 3);
21 | }
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c_float:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/iip_c_float
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/iip_c_float.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | float magnitude2(float x, float y, float z) {
4 | return x * x + y * y;
5 | }
6 |
7 | void test(float x, float y, float z) {
8 | float mag2 = magnitude2(x, y, z);
9 | printf("The square magnitude of the vector (%f, %f, %f) is %f\n", x, y, z, mag2);
10 | }
11 |
12 | void main() {
13 | test(0.0f, 0.0f, 0.0f);
14 | test(1.0f, 2.0f, 3.0f);
15 | test(-20.0f, 33.2f, 5.2f);
16 | test(3.0f, 4.0f, 0.0f);
17 | }
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/issue8:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/issue8
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/amd64/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/amd64/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/armhf/iip_c:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/armhf/iip_c
--------------------------------------------------------------------------------
/tests/test_binaries/armhf/iip_c.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | uint32_t add_2_then_double(uint32_t n) {
5 | uint32_t ret = n + 2;
6 | ret *= 2;
7 | return ret;
8 | }
9 |
10 | int main(int argc, char *argv[]) {
11 | uint32_t num = add_2_then_double(4);
12 | printf("%d", num);
13 | return 0;
14 | }
15 |
--------------------------------------------------------------------------------
/tests/test_binaries/armhf/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/armhf/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/armhf/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/armhf/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/armhf/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/armhf/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/mips/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/mips/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/mips/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/mips64/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/mips64/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/mips64/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/mips64el/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64el/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/mips64el/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64el/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/mips64el/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mips64el/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/mipsel/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mipsel/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/mipsel/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mipsel/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/mipsel/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/mipsel/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/ppc/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/ppc/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64le/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64le/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64le/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64le/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/ppc64le/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/ppc64le/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/s390x/printf.c:
--------------------------------------------------------------------------------
1 | #include
2 | int main() {
3 | printf("%s", "Hi");
4 | return 0;
5 | }
6 |
--------------------------------------------------------------------------------
/tests/test_binaries/s390x/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/s390x/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/s390x/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/s390x/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/s390x/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/s390x/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_binaries/s390x/replace_function_patch.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int max(int, int);
4 | int add(int, int);
5 | int subtract(int, int);
6 | int multiply(int, int);
7 | int divide(int, int);
8 | int print_n_times(int, int);
9 |
10 | int max(int a, int b){ return a >= b ? a : b; }
11 | int add(int a, int b){ for(;; b--, a++) if(b <= 0) return a; }
12 | int subtract(int a, int b){ for(;; b--, a--) if(b <= 0) return a; }
13 | int multiply(int a, int b){ for(int c = 0;; b = subtract(b, 1), c = add(c, a)) if(b <= 0) return c; }
14 | int divide(int a, int b){ for(int c = 0;; a = subtract(a, b), c = add(c, 1)) if (a < b) return c; }
15 | int print_n_times(int a, int n){ for(; n > 0; n--) printf("%d", a); }
16 |
17 | void main(){ print_n_times(multiply(add(1, 2), subtract(9, 2)), divide(10, max(2, 5))); }
18 |
--------------------------------------------------------------------------------
/tests/test_binaries/x86/printf_nopie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/x86/printf_nopie
--------------------------------------------------------------------------------
/tests/test_binaries/x86/printf_pie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/x86/printf_pie
--------------------------------------------------------------------------------
/tests/test_binaries/x86/replace_function_patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/purseclab/Patcherex2/5a985fd534e58368dfedaa4d2fc0692721f941a5/tests/test_binaries/x86/replace_function_patch
--------------------------------------------------------------------------------
/tests/test_ppc.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # ruff: noqa
4 | import logging
5 | import os
6 | import shutil
7 | import subprocess
8 | import tempfile
9 |
10 | import pytest
11 |
12 | from patcherex2 import *
13 |
14 | logging.getLogger("patcherex2").setLevel("DEBUG")
15 |
16 |
17 | class Tests:
18 | @pytest.fixture(autouse=True, scope="class", params=["angr", "ghidra"])
19 | def setup(self, request):
20 | request.cls.bin_location = str(
21 | os.path.join(
22 | os.path.dirname(os.path.realpath(__file__)),
23 | "./test_binaries/ppc",
24 | )
25 | )
26 | request.cls.binary_analyzer = request.param
27 |
28 | def test_raw_file_patch(self):
29 | self.run_one(
30 | "printf_nopie",
31 | [ModifyRawBytesPatch(0x6A4, b"No", addr_type="raw")],
32 | expected_output=b"No",
33 | expected_returnCode=0,
34 | )
35 |
36 | def test_raw_mem_patch(self):
37 | self.run_one(
38 | "printf_nopie",
39 | [ModifyRawBytesPatch(0x100006A4, b"No")],
40 | expected_output=b"No",
41 | expected_returnCode=0,
42 | )
43 |
44 | def test_modify_instruction_patch(self):
45 | self.run_one(
46 | "printf_nopie",
47 | [
48 | ModifyInstructionPatch(0x10000514, "addi 4, 9, 0x6ac"),
49 | ],
50 | expected_output=b"%s",
51 | expected_returnCode=0,
52 | )
53 |
54 | def test_insert_instruction_patch(self):
55 | instrs = """
56 | li 0, 0x4
57 | li 3, 1
58 | lis 9, 0x1000
59 | addi 4, 9, 0x6a4
60 | li 5, 0x3
61 | sc
62 | """
63 | self.run_one(
64 | "printf_nopie",
65 | [InsertInstructionPatch(0x10000528, instrs)],
66 | expected_output=b"Hi\x00Hi",
67 | expected_returnCode=0,
68 | )
69 |
70 | def test_insert_instruction_patch_2(self):
71 | instrs = """
72 | li 3, 0x32
73 | li 0, 0x1
74 | sc
75 | """
76 | self.run_one(
77 | "printf_nopie",
78 | [
79 | InsertInstructionPatch("return_0x32", instrs),
80 | ModifyInstructionPatch(0x10000528, "b {return_0x32}"),
81 | ],
82 | expected_returnCode=0x32,
83 | )
84 |
85 | def test_remove_instruction_patch(self):
86 | self.run_one(
87 | "printf_nopie",
88 | [
89 | RemoveInstructionPatch(0x100006A5, num_bytes=4),
90 | ],
91 | expected_output=b"H\x60",
92 | expected_returnCode=0,
93 | )
94 |
95 | def test_modify_data_patch(self):
96 | self.run_one(
97 | "printf_nopie",
98 | [ModifyDataPatch(0x100006A4, b"No")],
99 | expected_output=b"No",
100 | expected_returnCode=0,
101 | )
102 |
103 | def test_insert_data_patch(self, tlen=5):
104 | p1 = InsertDataPatch("added_data", b"A" * tlen)
105 | instrs = """
106 | li 0, 0x4
107 | li 3, 0x1
108 | lis 9, {added_data}@h
109 | addi 4, 9, {added_data}@l
110 | li 5, %s
111 | sc
112 | """ % hex(tlen)
113 | p2 = InsertInstructionPatch(0x10000528, instrs)
114 | self.run_one(
115 | "printf_nopie",
116 | [p1, p2],
117 | expected_output=b"A" * tlen + b"Hi",
118 | expected_returnCode=0,
119 | )
120 |
121 | def test_remove_data_patch(self):
122 | self.run_one(
123 | "printf_nopie",
124 | [RemoveDataPatch(0x100006A5, 1)],
125 | expected_output=b"H",
126 | expected_returnCode=0,
127 | )
128 |
129 | def test_replace_function_patch(self):
130 | code = """
131 | int add(int a, int b){ for(;; b--, a+=2) if(b <= 0) return a; }
132 | """
133 | self.run_one(
134 | "replace_function_patch",
135 | [ModifyFunctionPatch(0x100004FC, code)],
136 | expected_output=b"70707070",
137 | expected_returnCode=0,
138 | )
139 |
140 | @pytest.mark.skip(reason="waiting for cle relocation support")
141 | def test_replace_function_patch_with_function_reference(self):
142 | code = """
143 | extern int add(int, int);
144 | extern int subtract(int, int);
145 | int multiply(int a, int b){ for(int c = 0;; b = subtract(b, 1), c = subtract(c, a)) if(b <= 0) return c; }
146 | """
147 | self.run_one(
148 | "replace_function_patch",
149 | [ModifyFunctionPatch(0x100005AC, code)],
150 | expected_output=b"-21-21",
151 | expected_returnCode=0,
152 | )
153 |
154 | @pytest.mark.skip(reason="waiting for cle relocation support")
155 | def test_replace_function_patch_with_function_reference_and_rodata(self):
156 | code = """
157 | extern int printf(const char *format, ...);
158 | int multiply(int a, int b){ printf("%sWorld %s %s %s %d\\n", "Hello ", "Hello ", "Hello ", "Hello ", a * b);printf("%sWorld\\n", "Hello "); return a * b; }
159 | """
160 | self.run_one(
161 | "replace_function_patch",
162 | [ModifyFunctionPatch(0x100005AC, code)],
163 | expected_output=b"Hello World Hello Hello Hello 21\nHello World\n2121",
164 | expected_returnCode=0,
165 | )
166 |
167 | @pytest.mark.skip(reason="waiting for cle relocation support")
168 | def test_insert_function_patch(self):
169 | insert_code = """
170 | int min(int a, int b) { return (a < b) ? a : b; }
171 | """
172 | replace_code = """
173 | extern int min(int, int);
174 | int max(int a, int b) { return min(a, b); }
175 | """
176 | self.run_one(
177 | "replace_function_patch",
178 | [
179 | InsertFunctionPatch("min", insert_code),
180 | ModifyFunctionPatch(0x100006A8, replace_code),
181 | ],
182 | expected_output=b"2121212121",
183 | expected_returnCode=0,
184 | )
185 |
186 | def run_one(
187 | self,
188 | filename,
189 | patches,
190 | set_oep=None,
191 | inputvalue=None,
192 | expected_output=None,
193 | expected_returnCode=None,
194 | ):
195 | filepath = os.path.join(self.bin_location, filename)
196 | pipe = subprocess.PIPE
197 |
198 | with tempfile.TemporaryDirectory() as td:
199 | tmp_file = os.path.join(td, "patched")
200 | p = Patcherex(
201 | filepath, target_opts={"binary_analyzer": self.binary_analyzer}
202 | )
203 | for patch in patches:
204 | p.patches.append(patch)
205 | p.apply_patches()
206 | p.save_binary(tmp_file)
207 | p.shutdown()
208 | # os.system(f"readelf -hlS {tmp_file}")
209 |
210 | p = subprocess.Popen(
211 | ["qemu-ppc", "-L", "/usr/powerpc-linux-gnu", tmp_file],
212 | stdin=pipe,
213 | stdout=pipe,
214 | stderr=pipe,
215 | )
216 | res = p.communicate(inputvalue)
217 | if expected_output:
218 | if res[0] != expected_output:
219 | pytest.fail(
220 | f"AssertionError: {res[0]} != {expected_output}, binary dumped: {self.dump_file(tmp_file)}"
221 | )
222 | # self.assertEqual(res[0], expected_output)
223 | if expected_returnCode:
224 | if p.returncode != expected_returnCode:
225 | pytest.fail(
226 | f"AssertionError: {p.returncode} != {expected_returnCode}, binary dumped: {self.dump_file(tmp_file)}"
227 | )
228 | # self.assertEqual(p.returncode, expected_returnCode)
229 |
230 | def dump_file(self, file):
231 | shutil.copy(file, "/tmp/patcherex_failed_binary")
232 | return "/tmp/patcherex_failed_binary"
233 |
--------------------------------------------------------------------------------