├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ ├── nightly-check.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── clippy.toml ├── examples ├── cat_detour.rs ├── com_dxgi_present.rs ├── kernel32_detour.rs └── messageboxw_detour.rs ├── rustfmt.toml ├── src ├── alloc │ ├── mod.rs │ ├── proximity.rs │ └── search.rs ├── arch │ ├── detour.rs │ ├── memory.rs │ ├── mod.rs │ └── x86 │ │ ├── meta.rs │ │ ├── mod.rs │ │ ├── patcher.rs │ │ ├── thunk │ │ ├── mod.rs │ │ ├── x64.rs │ │ └── x86.rs │ │ └── trampoline │ │ ├── disasm.rs │ │ └── mod.rs ├── detours │ ├── generic.rs │ ├── mod.rs │ ├── raw.rs │ └── statik.rs ├── error.rs ├── lib.rs ├── macros.rs ├── pic │ ├── emitter.rs │ ├── mod.rs │ └── thunk.rs ├── traits.rs └── util.rs └── tests └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.rs] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Check/Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | target: 14 | - os: windows-latest 15 | triple: i686-pc-windows-msvc 16 | - os: windows-latest 17 | triple: x86_64-pc-windows-msvc 18 | # Use because of broken GH Action 19 | # https://github.com/egor-tensin/setup-mingw/issues/6#issuecomment-1215265290 20 | - os: windows-latest 21 | triple: i686-pc-windows-gnu 22 | - os: windows-latest 23 | triple: x86_64-pc-windows-gnu 24 | 25 | - os: ubuntu-latest 26 | triple: x86_64-unknown-linux-gnu 27 | # - os: ubuntu-latest 28 | # triple: x86_64-unknown-linux-musl 29 | # rustflags: RUSTFLAGS="-C target-feature=-crt-static" 30 | - os: ubuntu-latest 31 | triple: i686-unknown-linux-gnu 32 | 33 | # - os: macos-latest 34 | # triple: i686-apple-darwin 35 | # - os: macos-latest 36 | # triple: x86_64-apple-darwin 37 | 38 | runs-on: ${{ matrix.target.os }} 39 | steps: 40 | - uses: actions/checkout@v2 41 | 42 | - uses: dtolnay/rust-toolchain@master 43 | with: 44 | toolchain: nightly 45 | targets: ${{ matrix.target.triple }} 46 | - uses: dtolnay/rust-toolchain@master 47 | with: 48 | toolchain: stable 49 | targets: ${{ matrix.target.triple }} 50 | # Install linux deps 51 | - if: matrix.target.os == 'ubuntu-latest' 52 | run: sudo apt-get update && sudo apt-get install gcc-multilib 53 | # Install linux deps 54 | # - if: matrix.target.triple == 'x86_64-unknown-linux-musl' 55 | # run: sudo apt-get install musl-tools 56 | # Windows mingw 64bit 57 | - if: matrix.target.triple == 'x86_64-pc-windows-gnu' 58 | name: Set up MinGW 59 | uses: egor-tensin/setup-mingw@v2 60 | with: 61 | platform: x64 62 | version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14 63 | # Windows mingw 32bit 64 | - if: matrix.target.triple == 'i686-pc-windows-gnu' 65 | name: Set up MinGW 66 | uses: egor-tensin/setup-mingw@v2 67 | with: 68 | platform: x86 69 | version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14 70 | - name: Cargo Check - Default Features Only 71 | run: ${{ matrix.target.rustflags }} cargo check --target ${{ matrix.target.triple }} 72 | 73 | - name: Cargo Check - All Features 74 | run: ${{ matrix.target.rustflags }} cargo +nightly check --target ${{ matrix.target.triple }} --all-features 75 | 76 | - name: Cargo Tests - Stable 77 | run: ${{ matrix.target.rustflags }} cargo test --target ${{ matrix.target.triple }} 78 | 79 | - name: Cargo Tests - Nightly 80 | run: ${{ matrix.target.rustflags }} cargo +nightly test --target ${{ matrix.target.triple }} --all-features 81 | 82 | -------------------------------------------------------------------------------- /.github/workflows/nightly-check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | # Run once a week 4 | - cron: "0 0 * * 0" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | nightly-test: 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: dtolnay/rust-toolchain@master 17 | with: 18 | toolchain: nightly 19 | # Install linux deps 20 | - if: matrix.os == 'ubuntu-latest' 21 | run: sudo apt-get update && sudo apt-get install gcc-multilib 22 | 23 | - name: Cargo Check - All Features 24 | run: cargo +nightly check --all-features 25 | 26 | - name: Cargo Tests - Nightly 27 | run: cargo +nightly test --all-features 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Open a release PR 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Version to release 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | make-release-pr: 13 | permissions: 14 | id-token: write # Enable OIDC 15 | pull-requests: write 16 | contents: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: chainguard-dev/actions/setup-gitsign@main 21 | 22 | - name: Check semver 23 | uses: obi1kenobi/cargo-semver-checks-action@v1 24 | 25 | - name: Install cargo-release 26 | uses: taiki-e/install-action@v1 27 | with: 28 | tool: cargo-release 29 | 30 | - uses: cargo-bins/release-pr@v2 31 | with: 32 | github-token: ${{ secrets.GITHUB_TOKEN }} 33 | version: ${{ inputs.version }} 34 | pr-release-notes: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.8.0 (2021-05-10) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ### Chore 23 | 24 | - update version 0.7.1 → 0.8.0 25 | - add linux musl & i686-gnu targets 26 | - add custom macOS linker for RWX execution 27 | - add CI for rust stable 28 | - use latest macOS iamge (10.15) 29 | - disable 'i686-apple-darwin' CI 30 | Since macOS x86 is now a tier 3 target, the advantages of supporting 31 | this target has greatly diminished. For now, it's CI suite will be 32 | disabled. 33 | - update 'cfg-if' dependency 34 | - use shield badges for Azure 35 | - simplify target configuration 36 | - update dependencies 37 | 38 | ### Bug Fixes 39 | 40 | - replace deprecated features 41 | - remove deprecated '--all' flag 42 | - 32-bit range calculation overflow 43 | - replace obsolete 'kernel32-sys' dependency 44 | - use new 'Protection' constants 45 | - replace deprecated 'asm!' 46 | 47 | ### Style 48 | 49 | - apply rustfmt 50 | 51 | ### Commit Statistics 52 | 53 | 54 | 55 | - 18 commits contributed to the release. 56 | - 624 days passed between releases. 57 | - 17 commits were understood as [conventional](https://www.conventionalcommits.org). 58 | - 0 issues like '(#ID)' were seen in commit messages 59 | 60 | ### Commit Details 61 | 62 | 63 | 64 |
view details 65 | 66 | * **Uncategorized** 67 | - Update version 0.7.1 → 0.8.0 ([`07b3465`](https://github.com/Hpmason/retour-rs/commit/07b346570c69736a57a25212d7121309711ee50b)) 68 | - Apply rustfmt ([`9c7dcb0`](https://github.com/Hpmason/retour-rs/commit/9c7dcb0cbbd98c39f195b18e968fc34258d6fbfe)) 69 | - Add unstable feature use of `const_fn_trait_bound` ([`44f2111`](https://github.com/Hpmason/retour-rs/commit/44f2111ab533c7aa750ef7800a1904a8581907dd)) 70 | - Replace deprecated features ([`5bdab40`](https://github.com/Hpmason/retour-rs/commit/5bdab402e69b43bebf4ff0c793ce06845b1359f0)) 71 | - Remove deprecated '--all' flag ([`6c1d92c`](https://github.com/Hpmason/retour-rs/commit/6c1d92cd50bc082ca4607c6983c598206ddc1b63)) 72 | - Add linux musl & i686-gnu targets ([`5808bed`](https://github.com/Hpmason/retour-rs/commit/5808bed377134c7f5d7f7f45ea10154d6b06dab5)) 73 | - Add custom macOS linker for RWX execution ([`6af8b28`](https://github.com/Hpmason/retour-rs/commit/6af8b287e7215d5226eede7cb623e9841afcc550)) 74 | - Add CI for rust stable ([`aeadc00`](https://github.com/Hpmason/retour-rs/commit/aeadc0032eb1631bedd40476b6211fd41207375a)) 75 | - Use latest macOS iamge (10.15) ([`1aa9cc8`](https://github.com/Hpmason/retour-rs/commit/1aa9cc8b795abcb4b0028ff2ed5cef29e8c21a17)) 76 | - Disable 'i686-apple-darwin' CI ([`f792981`](https://github.com/Hpmason/retour-rs/commit/f792981ebb34228d34b444e5a4d736d18aa0fdf5)) 77 | - 32-bit range calculation overflow ([`ee9f820`](https://github.com/Hpmason/retour-rs/commit/ee9f8205150d0f9a82b591e810a6c249f0b1bf6d)) 78 | - Update 'cfg-if' dependency ([`b840d9e`](https://github.com/Hpmason/retour-rs/commit/b840d9eef1260959764f00b7161fa8aa33a3c095)) 79 | - Replace obsolete 'kernel32-sys' dependency ([`f2c3f4c`](https://github.com/Hpmason/retour-rs/commit/f2c3f4c5f4bbcb9b2c49a03f9843e06b6d55766a)) 80 | - Use shield badges for Azure ([`d651621`](https://github.com/Hpmason/retour-rs/commit/d651621f2c2767f0dd52690298bbe9453f4ac8e5)) 81 | - Simplify target configuration ([`28c0fde`](https://github.com/Hpmason/retour-rs/commit/28c0fdeff412f04f4dc398bd9c1ecab98f7dbc29)) 82 | - Update dependencies ([`3f7f55b`](https://github.com/Hpmason/retour-rs/commit/3f7f55b189144086d95fc72de4aa3347e7aa6e3b)) 83 | - Use new 'Protection' constants ([`e6fa25f`](https://github.com/Hpmason/retour-rs/commit/e6fa25f6425429f59dd9b07a0c2c4ff6423c855a)) 84 | - Replace deprecated 'asm!' ([`eb49d95`](https://github.com/Hpmason/retour-rs/commit/eb49d95122a08ca44e54d8b59811789a3d872a7c)) 85 |
86 | 87 | ## 0.7.1 (2019-08-25) 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ### Chore 97 | 98 | - update version 0.7.0 → 0.7.1 99 | - use azure for all CI 100 | - add missing 32-bit libs 101 | - add azure pipelines configuration 102 | 103 | ### New Features 104 | 105 | - add support for scoped visibility 106 | - test latest udis86 107 | - update dependencies 108 | 109 | ### Bug Fixes 110 | 111 | - use the latest published libudis86 112 | - disable GNU targets for azure 113 | - explicitly drop boxed data 114 | - use atomic for detour enabled state 115 | 116 | ### Refactor 117 | 118 | - use inner functions for tests 119 | - prevent 'FreeRegionIter' from being exposed 120 | 121 | ### Commit Statistics 122 | 123 | 124 | 125 | - 13 commits contributed to the release. 126 | - 76 days passed between releases. 127 | - 13 commits were understood as [conventional](https://www.conventionalcommits.org). 128 | - 0 issues like '(#ID)' were seen in commit messages 129 | 130 | ### Commit Details 131 | 132 | 133 | 134 |
view details 135 | 136 | * **Uncategorized** 137 | - Update version 0.7.0 → 0.7.1 ([`23c7fb4`](https://github.com/Hpmason/retour-rs/commit/23c7fb478c8312da6609ec5b81a83b214a5db8d7)) 138 | - Use the latest published libudis86 ([`e2c8866`](https://github.com/Hpmason/retour-rs/commit/e2c8866990d1e665608c16b4ec2576773efd0400)) 139 | - Use azure for all CI ([`c41834b`](https://github.com/Hpmason/retour-rs/commit/c41834bca87889591093a987683e0768f343c6fd)) 140 | - Disable GNU targets for azure ([`e658cfc`](https://github.com/Hpmason/retour-rs/commit/e658cfcdcc00df31d9052dff2d8cd32ad4ff900b)) 141 | - Explicitly drop boxed data ([`a099038`](https://github.com/Hpmason/retour-rs/commit/a099038a15c356a48c0352395e7ef46e77b7650e)) 142 | - Add support for scoped visibility ([`09cda49`](https://github.com/Hpmason/retour-rs/commit/09cda49357b3c0a8b19b888415b6ea971c63bd24)) 143 | - Test latest udis86 ([`03ca7e4`](https://github.com/Hpmason/retour-rs/commit/03ca7e47a777bbbca3c1272e30b34757051d50ed)) 144 | - Add missing 32-bit libs ([`4de8f01`](https://github.com/Hpmason/retour-rs/commit/4de8f01736147cd28bc0ce4c69a397773975aa99)) 145 | - Add azure pipelines configuration ([`53997fa`](https://github.com/Hpmason/retour-rs/commit/53997fa75416c06cc5baebde59cc046da0d84f51)) 146 | - Use inner functions for tests ([`b8170ff`](https://github.com/Hpmason/retour-rs/commit/b8170fff2ae6f3f5e5b3c66d5ea010e70feecbf2)) 147 | - Update dependencies ([`49cfaf3`](https://github.com/Hpmason/retour-rs/commit/49cfaf330bdfc0e50aca7f22aafad68eab101f9a)) 148 | - Use atomic for detour enabled state ([`a12175c`](https://github.com/Hpmason/retour-rs/commit/a12175c7bf25a29367df210330229e22f7d1c444)) 149 | - Prevent 'FreeRegionIter' from being exposed ([`e5124e1`](https://github.com/Hpmason/retour-rs/commit/e5124e13a79de218b6c2320d935da135f09da3ed)) 150 |
151 | 152 | ## 0.7.0 (2019-06-10) 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | ### Chore 166 | 167 | - update version 6.0 → 7.0 168 | 169 | ### Documentation 170 | 171 | - add 'MessageBoxW' detour example 172 | - update comments 173 | - update macro/detour comments 174 | 175 | ### New Features 176 | 177 | - return '&self' from 'initialize' 178 | - migrate to '2018' edition 179 | - implement std 'Error' trait 180 | 181 | ### Bug Fixes 182 | 183 | - add missing 'dyn' specifiers 184 | - temporarily remove 'musl' CI tests 185 | Cargo does not enable OS/cfg specific examples, so the 'cdylib' tests 186 | errors when using 'musl' due to not supporting the crate type. 187 | Temporarily remove these targets until a workaround can be implemented. 188 | - use 'dyn' for dynamic dispatches 189 | - remove 'const_fn' thanks to 2018 macros 190 | - remove 'ref' prefix 191 | - iterator for descending regions 192 | 193 | ### Refactor 194 | 195 | - use stabilized range 'contains' 196 | - use 'expect' prior to 'unwrap' 197 | - remove 'tap' dependency 198 | - remove 'Boolinator' dependency 199 | - simplify 'StaticDetour' API 200 | - use 'static ref' instead of 'struct' 201 | 202 | ### Style 203 | 204 | - apply rustfmt 205 | - normalize macro indentation 206 | - normalize indentation 207 | 208 | ### Commit Statistics 209 | 210 | 211 | 212 | - 22 commits contributed to the release. 213 | - 157 days passed between releases. 214 | - 22 commits were understood as [conventional](https://www.conventionalcommits.org). 215 | - 0 issues like '(#ID)' were seen in commit messages 216 | 217 | ### Commit Details 218 | 219 | 220 | 221 |
view details 222 | 223 | * **Uncategorized** 224 | - Update version 6.0 → 7.0 ([`7ff857d`](https://github.com/Hpmason/retour-rs/commit/7ff857d5d8cf1e02d11552df03070ef130589ebf)) 225 | - Add missing 'dyn' specifiers ([`03b8db9`](https://github.com/Hpmason/retour-rs/commit/03b8db911b1028c04d686c076532624e3e0df119)) 226 | - Temporarily remove 'musl' CI tests ([`5e85585`](https://github.com/Hpmason/retour-rs/commit/5e85585a9522d47ec874f5281fc8b2ec20262fd4)) 227 | - Add 'MessageBoxW' detour example ([`81ee843`](https://github.com/Hpmason/retour-rs/commit/81ee8436255e339e136a1a1562762e5ab9687996)) 228 | - Use 'dyn' for dynamic dispatches ([`7d799b2`](https://github.com/Hpmason/retour-rs/commit/7d799b203cb56b955a7fad21413ea64ce08e35aa)) 229 | - Return '&self' from 'initialize' ([`10ce422`](https://github.com/Hpmason/retour-rs/commit/10ce4225359f8031eafbd9166297cdee3ccd3f6b)) 230 | - Update comments ([`1ba909e`](https://github.com/Hpmason/retour-rs/commit/1ba909e81d01c6b080f7e25b11885c9fbefb45a0)) 231 | - Use stabilized range 'contains' ([`073e0e7`](https://github.com/Hpmason/retour-rs/commit/073e0e740941cc76cd0279697bfa53ae0d7bdc93)) 232 | - Remove 'const_fn' thanks to 2018 macros ([`a4a4172`](https://github.com/Hpmason/retour-rs/commit/a4a4172a1c979c7295e861bb0a8c0175baf8bc9b)) 233 | - Apply rustfmt ([`ab9f22a`](https://github.com/Hpmason/retour-rs/commit/ab9f22aab30f35b49f77b84ff0d43c48757e5f53)) 234 | - Migrate to '2018' edition ([`6682e77`](https://github.com/Hpmason/retour-rs/commit/6682e7725f2ae7b4af0f1b3712e588e97c047753)) 235 | - Use 'expect' prior to 'unwrap' ([`693abce`](https://github.com/Hpmason/retour-rs/commit/693abce46d859e8adfb287542531fa081fe029b1)) 236 | - Remove 'ref' prefix ([`1ac7403`](https://github.com/Hpmason/retour-rs/commit/1ac74031a6b85e1d5533434b6407a7dbb0aa035f)) 237 | - Update macro/detour comments ([`6b7b2f7`](https://github.com/Hpmason/retour-rs/commit/6b7b2f726f637239a025ecffe6ebb7d76d6e32c7)) 238 | - Remove 'tap' dependency ([`80cd96f`](https://github.com/Hpmason/retour-rs/commit/80cd96f83f3936a77a411b24b8a08d8c8623b9a8)) 239 | - Remove 'Boolinator' dependency ([`dd8dbd0`](https://github.com/Hpmason/retour-rs/commit/dd8dbd04a180c90a2861c9eea3b15d6b412695b4)) 240 | - Simplify 'StaticDetour' API ([`9a9c143`](https://github.com/Hpmason/retour-rs/commit/9a9c14348a5692ea241d50ac6c069f8a67c3f041)) 241 | - Use 'static ref' instead of 'struct' ([`24f2ce1`](https://github.com/Hpmason/retour-rs/commit/24f2ce11dbc65e4807f969cbd0c889531d89e067)) 242 | - Normalize macro indentation ([`46cda10`](https://github.com/Hpmason/retour-rs/commit/46cda1077ad0f97886a421bc1e8483000b5c0e78)) 243 | - Implement std 'Error' trait ([`4ce4112`](https://github.com/Hpmason/retour-rs/commit/4ce411265609aad929e5ccaaf21bc0d8b5743827)) 244 | - Iterator for descending regions ([`eeb82ca`](https://github.com/Hpmason/retour-rs/commit/eeb82cae86ede737431d36fdcd6c8a6eaa381621)) 245 | - Normalize indentation ([`7d80778`](https://github.com/Hpmason/retour-rs/commit/7d80778ebd38af65d5abf7a27983fe796d9430fa)) 246 |
247 | 248 | ## 0.6.0 (2019-01-03) 249 | 250 | 251 | 252 | 253 | 254 | ### Chore 255 | 256 | - update version 0.5 → 0.6 257 | This version greatly decreases the compile time and total number of 258 | required dependencies. 259 | - update dependencies 260 | - add windows (GNU) to travis config 261 | 262 | ### Bug Fixes 263 | 264 | - remove 'failure' as dependency 265 | 266 | ### Commit Statistics 267 | 268 | 269 | 270 | - 6 commits contributed to the release. 271 | - 234 days passed between releases. 272 | - 4 commits were understood as [conventional](https://www.conventionalcommits.org). 273 | - 0 issues like '(#ID)' were seen in commit messages 274 | 275 | ### Commit Details 276 | 277 | 278 | 279 |
view details 280 | 281 | * **Uncategorized** 282 | - Update version 0.5 → 0.6 ([`bfa4c41`](https://github.com/Hpmason/retour-rs/commit/bfa4c4100ffd21d0b276ace23e11b88616fe1f57)) 283 | - Update dependencies ([`4dc337d`](https://github.com/Hpmason/retour-rs/commit/4dc337ddc2d505adae55d0a666ab77b122f608b9)) 284 | - Remove 'failure' as dependency ([`c54bb3c`](https://github.com/Hpmason/retour-rs/commit/c54bb3c136013610e1271b37bea2588a3be7b622)) 285 | - Add windows (GNU) to travis config ([`5cd38b5`](https://github.com/Hpmason/retour-rs/commit/5cd38b50f05827ca42990717ac34eade8c3457ee)) 286 | - Update static_detours to support "thiscall" convention ([`19c88cd`](https://github.com/Hpmason/retour-rs/commit/19c88cd02b766d7bc3ad70e7f72f7c13e0c4de23)) 287 | - Bump generic-array and tap versions ([`7af703d`](https://github.com/Hpmason/retour-rs/commit/7af703dab98f0cc63be0b10531ebda29ce62d00f)) 288 |
289 | 290 | ## 0.5.0 (2018-05-14) 291 | 292 | 293 | 294 | ### Chore 295 | 296 | - update editorconfig; matches rustfmt.toml 297 | 298 | ### New Features 299 | 300 | - update version → 0.5 301 | 302 | ### Bug Fixes 303 | 304 | - apply clippy recommendations 305 | - use local Error type instead of failure 306 | 307 | ### Commit Statistics 308 | 309 | 310 | 311 | - 4 commits contributed to the release. 312 | - 49 days passed between releases. 313 | - 4 commits were understood as [conventional](https://www.conventionalcommits.org). 314 | - 0 issues like '(#ID)' were seen in commit messages 315 | 316 | ### Commit Details 317 | 318 | 319 | 320 |
view details 321 | 322 | * **Uncategorized** 323 | - Update version → 0.5 ([`8b88bd2`](https://github.com/Hpmason/retour-rs/commit/8b88bd213eb24e9d26a564df156db19d0fa9f605)) 324 | - Apply clippy recommendations ([`25f5cc1`](https://github.com/Hpmason/retour-rs/commit/25f5cc1bf60c3ceb1e9d28fd8c5f2b584f4d2646)) 325 | - Update editorconfig; matches rustfmt.toml ([`70931ee`](https://github.com/Hpmason/retour-rs/commit/70931eec858b83ff41df79a7f63e1bc496364575)) 326 | - Use local Error type instead of failure ([`ecb9d87`](https://github.com/Hpmason/retour-rs/commit/ecb9d87d20ed7c69fa22972e96aef4a51aa21cb5)) 327 |
328 | 329 | ## 0.4.1 (2018-03-26) 330 | 331 | 332 | 333 | 334 | ### Documentation 335 | 336 | - add distinction between travis/appveyor 337 | 338 | ### New Features 339 | 340 | - update version → 0.4.1 341 | - add clippy config 342 | - add rustfmt config 343 | 344 | ### Bug Fixes 345 | 346 | - scoping issue with 1.26 nightly 347 | 348 | ### Refactor 349 | 350 | - use doc for attribute test 351 | 352 | ### Style 353 | 354 | - format project using rustfmt 355 | 356 | ### Commit Statistics 357 | 358 | 359 | 360 | - 9 commits contributed to the release. 361 | - 20 days passed between releases. 362 | - 7 commits were understood as [conventional](https://www.conventionalcommits.org). 363 | - 0 issues like '(#ID)' were seen in commit messages 364 | 365 | ### Commit Details 366 | 367 | 368 | 369 |
view details 370 | 371 | * **Uncategorized** 372 | - Update version → 0.4.1 ([`9e7f222`](https://github.com/Hpmason/retour-rs/commit/9e7f222d00f3aa178910a53ec2bf948fc49869fc)) 373 | - Add clippy config ([`a0419ff`](https://github.com/Hpmason/retour-rs/commit/a0419ffd3a2a16aa19c85cc6b994f2e25a95b140)) 374 | - Format project using rustfmt ([`927ab0a`](https://github.com/Hpmason/retour-rs/commit/927ab0a87528b4fc381c71dd8b587b8ebda726b9)) 375 | - Add rustfmt config ([`f3d0c05`](https://github.com/Hpmason/retour-rs/commit/f3d0c05a91f7b35817849b00f730e103d1c3ae1d)) 376 | - Use doc for attribute test ([`f7d34c8`](https://github.com/Hpmason/retour-rs/commit/f7d34c80ed909bd833cf3aeec62bf3a7c0e85512)) 377 | - Scoping issue with 1.26 nightly ([`87fec81`](https://github.com/Hpmason/retour-rs/commit/87fec81e73ee7fc2d7367216b9be596487139d55)) 378 | - Add distinction between travis/appveyor ([`31bc37a`](https://github.com/Hpmason/retour-rs/commit/31bc37a3e5278191f306162f5451769e1804f76b)) 379 | - Merge branch 'kgv-master' ([`48de87a`](https://github.com/Hpmason/retour-rs/commit/48de87acb00bd4787f3a94c846fd8622ede95452)) 380 | - Merge branch 'master' into master ([`f1a7055`](https://github.com/Hpmason/retour-rs/commit/f1a70554fa72f8ff35ef441a965e5b4012141b3f)) 381 |
382 | 383 | ## 0.4.0 (2018-03-05) 384 | 385 | 386 | 387 | 388 | ### New Features 389 | 390 | - update version → 0.4 391 | - update dependencies 392 | - improve architecture abstraction. 393 | This change includes a ton of refactoring that should have been split 394 | into multiple atomic commits. The architecture specific code has a much 395 | improved abstraction whilst the nightly feature is no longer required. 396 | 397 | ### Bug Fixes 398 | 399 | 400 | 401 | 402 | - use wrapping_add in handle_relative_branch 403 | * displacement can be negative in some cases 404 | 405 | ### Refactor 406 | 407 | - StaticThunk → FixedThunk 408 | - Closure → Vec for x64 thunks 409 | 410 | ### Commit Statistics 411 | 412 | 413 | 414 | - 8 commits contributed to the release. 415 | - 151 days passed between releases. 416 | - 8 commits were understood as [conventional](https://www.conventionalcommits.org). 417 | - 0 issues like '(#ID)' were seen in commit messages 418 | 419 | ### Commit Details 420 | 421 | 422 | 423 |
view details 424 | 425 | * **Uncategorized** 426 | - Update version → 0.4 ([`192ce59`](https://github.com/Hpmason/retour-rs/commit/192ce59c1b1031b0046d3329a00f100f7c3d2f06)) 427 | - Update dependencies ([`785e344`](https://github.com/Hpmason/retour-rs/commit/785e3440be6292c1d134032c370d53f8ba23bf78)) 428 | - StaticThunk → FixedThunk ([`873270f`](https://github.com/Hpmason/retour-rs/commit/873270f654469e8c9dbe801b2795745261d072ab)) 429 | - Improve architecture abstraction. ([`254a295`](https://github.com/Hpmason/retour-rs/commit/254a29598cf2888b52deb407bbb1876d13e8e32f)) 430 | - Closure → Vec for x64 thunks ([`7d20fc0`](https://github.com/Hpmason/retour-rs/commit/7d20fc09c11a91e50b042dcd44db2c4d0ebdd676)) 431 | - Use wrapping_add in handle_relative_branch ([`5d708e9`](https://github.com/Hpmason/retour-rs/commit/5d708e960da12239f66a618e127c3e234acec04c)) 432 | - Static_detours macro parse meta attributes #4 ([`8eedeba`](https://github.com/Hpmason/retour-rs/commit/8eedeba920eebc771e832ba9d10643ff78210d02)) 433 | - Generate 'call' method for unsafe types ([`d11d729`](https://github.com/Hpmason/retour-rs/commit/d11d7299a44b196c5353fdbdd8302d5e7b3fb503)) 434 |
435 | 436 | ## 0.4.0-alpha.3 (2024-10-24) 437 | 438 | ### Commit Statistics 439 | 440 | 441 | 442 | - 8 commits contributed to the release. 443 | - 344 days passed between releases. 444 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). 445 | - 6 unique issues were worked on: [#41](https://github.com/Hpmason/retour-rs/issues/41), [#50](https://github.com/Hpmason/retour-rs/issues/50), [#53](https://github.com/Hpmason/retour-rs/issues/53), [#54](https://github.com/Hpmason/retour-rs/issues/54), [#55](https://github.com/Hpmason/retour-rs/issues/55), [#61](https://github.com/Hpmason/retour-rs/issues/61) 446 | 447 | ### Commit Details 448 | 449 | 450 | 451 |
view details 452 | 453 | * **[#41](https://github.com/Hpmason/retour-rs/issues/41)** 454 | - Replace udis with iced-x86 ([`19f2132`](https://github.com/Hpmason/retour-rs/commit/19f213249b765d23a1cdde3f0b7c7989838d19d2)) 455 | * **[#50](https://github.com/Hpmason/retour-rs/issues/50)** 456 | - Fix temp window not disappearing in dxgi present example ([`6496309`](https://github.com/Hpmason/retour-rs/commit/649630931c9c7e8ccf0d38fbdd44d720ebba9690)) 457 | * **[#53](https://github.com/Hpmason/retour-rs/issues/53)** 458 | - Support Detouring Functions With >14 Args ([`ad52613`](https://github.com/Hpmason/retour-rs/commit/ad526130f345bf2fcf2dabfec137a6cb2b88e2a9)) 459 | * **[#54](https://github.com/Hpmason/retour-rs/issues/54)** 460 | - Fix windows-gnu ci by downgrading mingw version ([`cde49b9`](https://github.com/Hpmason/retour-rs/commit/cde49b9d524d0b375b33650c14ed30def2f8cff2)) 461 | * **[#55](https://github.com/Hpmason/retour-rs/issues/55)** 462 | - Fix detour not disabling on drop in release mode ([`3bab630`](https://github.com/Hpmason/retour-rs/commit/3bab630e234528e848c6c4e0a81656d262224579)) 463 | * **[#61](https://github.com/Hpmason/retour-rs/issues/61)** 464 | - Replace asm! in naked functions with naked_asm! after new nightly update ([`e063c32`](https://github.com/Hpmason/retour-rs/commit/e063c3275d28ebc853cb058aa78d4b29eb8d7340)) 465 | * **Uncategorized** 466 | - Release retour v0.4.0-alpha.3 ([`f5f2429`](https://github.com/Hpmason/retour-rs/commit/f5f24292f83c97871b08692c08a53279caed658e)) 467 | - Adjusting changelogs prior to release of retour v0.4.0-alpha.3 ([`7258db2`](https://github.com/Hpmason/retour-rs/commit/7258db2647a7b3f785dc50ed639d93c5a4c574ff)) 468 |
469 | 470 | ## 0.4.0-alpha.2 (2023-11-15) 471 | 472 | ### New Features 473 | 474 | - add trailing comma support 475 | 476 | ### Bug Fixes 477 | 478 | - remove feature requirement for thiscall 479 | * fix: remove feature requirement for thiscall 480 | abi_thiscall is now stabilized rust and no longer requires nightly rust 481 | https://github.com/rust-lang/rust/commit/06daa9e263db87b3c5d4d80110938130db846183 482 | * fix: remove feature attribute definition 483 | * Keep thiscall-abi feature gate for back compat 484 | * Update docs for required thiscall-abi version 485 | 486 | ### Commit Statistics 487 | 488 | 489 | 490 | - 4 commits contributed to the release. 491 | - 63 days passed between releases. 492 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). 493 | - 2 unique issues were worked on: [#34](https://github.com/Hpmason/retour-rs/issues/34), [#45](https://github.com/Hpmason/retour-rs/issues/45) 494 | 495 | ### Commit Details 496 | 497 | 498 | 499 |
view details 500 | 501 | * **[#34](https://github.com/Hpmason/retour-rs/issues/34)** 502 | - Remove feature requirement for thiscall ([`3bf7863`](https://github.com/Hpmason/retour-rs/commit/3bf7863bc0ad4ba1d0657e6ee98d43145c16b658)) 503 | * **[#45](https://github.com/Hpmason/retour-rs/issues/45)** 504 | - Add trailing comma support ([`faaeec3`](https://github.com/Hpmason/retour-rs/commit/faaeec330e42fe3c34b4e3abcaf24d12fdf0884c)) 505 | * **Uncategorized** 506 | - Release retour v0.4.0-alpha.2 ([`a5be6c2`](https://github.com/Hpmason/retour-rs/commit/a5be6c2fd40955dcee0e877367b0ae80a2f8f67f)) 507 | - Bump version ([`4e0d383`](https://github.com/Hpmason/retour-rs/commit/4e0d383eea2c25d93d537d3e89db5ba5e38f8f57)) 508 |
509 | 510 | ## 0.4.0-alpha.1 (2023-09-12) 511 | 512 | ### Commit Statistics 513 | 514 | 515 | 516 | - 12 commits contributed to the release. 517 | - 98 days passed between releases. 518 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). 519 | - 6 unique issues were worked on: [#24](https://github.com/Hpmason/retour-rs/issues/24), [#26](https://github.com/Hpmason/retour-rs/issues/26), [#30](https://github.com/Hpmason/retour-rs/issues/30), [#31](https://github.com/Hpmason/retour-rs/issues/31), [#38](https://github.com/Hpmason/retour-rs/issues/38), [#39](https://github.com/Hpmason/retour-rs/issues/39) 520 | 521 | ### Commit Details 522 | 523 | 524 | 525 |
view details 526 | 527 | * **[#24](https://github.com/Hpmason/retour-rs/issues/24)** 528 | - Improve README.md ([`8a8ae9e`](https://github.com/Hpmason/retour-rs/commit/8a8ae9e8963cc77eb580dc1b5624f2242a9f8d1c)) 529 | * **[#26](https://github.com/Hpmason/retour-rs/issues/26)** 530 | - COM example: DXGI swapchain Present hook ([`8dca3f2`](https://github.com/Hpmason/retour-rs/commit/8dca3f2aa0ff3e4da486a6099e2c61e2c1c47c1b)) 531 | * **[#30](https://github.com/Hpmason/retour-rs/issues/30)** 532 | - Use slice-pool2 ([`629399a`](https://github.com/Hpmason/retour-rs/commit/629399a0e853adf774ba116e0a1d9f0af941ede9)) 533 | * **[#31](https://github.com/Hpmason/retour-rs/issues/31)** 534 | - Fix import static detour ([`ad33b38`](https://github.com/Hpmason/retour-rs/commit/ad33b38a40f4fa9cf5562f60b3b28126b5547823)) 535 | * **[#38](https://github.com/Hpmason/retour-rs/issues/38)** 536 | - Document Supported Versions ([`07085b0`](https://github.com/Hpmason/retour-rs/commit/07085b083cd2895227d3e55bb30884e34a49fb29)) 537 | * **[#39](https://github.com/Hpmason/retour-rs/issues/39)** 538 | - Expose trampoline() on Generic and Static Detour ([`5e63f92`](https://github.com/Hpmason/retour-rs/commit/5e63f92932652d98e987f0bef172987e703b4e63)) 539 | * **Uncategorized** 540 | - Pre-publish for v0.4.0-alpha.1 ([`0e86431`](https://github.com/Hpmason/retour-rs/commit/0e86431d05feff70c6455676a1c0e3492f7bd2af)) 541 | - Fix nightly-check.yml ([`a58667e`](https://github.com/Hpmason/retour-rs/commit/a58667e830eed4a6f7ae5fea8d0359b6e749dd82)) 542 | - Fix nightly-check.yml ([`e078224`](https://github.com/Hpmason/retour-rs/commit/e07822436029505ecbfa4bd02eb707fd6b993bee)) 543 | - Fix nightly-check.yml ([`e116704`](https://github.com/Hpmason/retour-rs/commit/e116704b8d4934d0c6fbeabec9726d41a1ae2795)) 544 | - Adjusting changelogs prior to release of retour v0.3.1 ([`f65a8be`](https://github.com/Hpmason/retour-rs/commit/f65a8be0c639d7fd302b8b4a42ce175db78f4933)) 545 | - Add changelog ([`c977a43`](https://github.com/Hpmason/retour-rs/commit/c977a438da96485c51f236fb370fa19a6f67bb95)) 546 |
547 | 548 | ## 0.3.1 (2023-07-18) 549 | 550 | ## v0.3.0 (2017-10-05) 551 | 552 | 553 | 554 | ### Bug Fixes 555 | 556 | - use features for latest nightly 557 | 558 | ### New Features 559 | 560 | - update version 0.2 → 0.3 561 | 562 | ### Chore 563 | 564 | - Release retour version 0.3.0 565 | 566 | ### Commit Statistics 567 | 568 | 569 | 570 | - 2 commits contributed to the release. 571 | - 21 days passed between releases. 572 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). 573 | - 0 issues like '(#ID)' were seen in commit messages 574 | 575 | ### Commit Details 576 | 577 | 578 | 579 |
view details 580 | 581 | * **Uncategorized** 582 | - Update version 0.2 → 0.3 ([`4e3676f`](https://github.com/Hpmason/retour-rs/commit/4e3676f1722eb67c6a90788ea6cc3f3e23d99d42)) 583 | - Use features for latest nightly ([`9581661`](https://github.com/Hpmason/retour-rs/commit/958166199c544d28b70344e6abd261270ac0e8df)) 584 |
585 | 586 | ## v0.2.0 (2017-09-14) 587 | 588 | 589 | 590 | 591 | 592 | ### New Features 593 | 594 | - update version 0.1.1 → 0.2 595 | - update dependency versions. 596 | Also remove backtrace from `error-chain` for XP support. 597 | 598 | ### Chore 599 | 600 | - Release retour version 0.2.0 601 | - squelch various lints 602 | 603 | ### Bug Fixes 604 | 605 | - GenericDetour doesn't accept unsafe fns 606 | 607 | ### Other 608 | 609 | - update ctor 610 | 611 | ### Commit Statistics 612 | 613 | 614 | 615 | - 4 commits contributed to the release. 616 | - 83 days passed between releases. 617 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). 618 | - 0 issues like '(#ID)' were seen in commit messages 619 | 620 | ### Commit Details 621 | 622 | 623 | 624 |
view details 625 | 626 | * **Uncategorized** 627 | - Update version 0.1.1 → 0.2 ([`7c7a75f`](https://github.com/Hpmason/retour-rs/commit/7c7a75fc84d04a2aeb60acfcfd64dd8966f27ffd)) 628 | - Update dependency versions. ([`ff71a86`](https://github.com/Hpmason/retour-rs/commit/ff71a86a6c4b5a4093ca053f391b813a119e3f4f)) 629 | - Merge pull request #2 from nick70/master ([`c585dfd`](https://github.com/Hpmason/retour-rs/commit/c585dfd6cb1724c75b11b88c557e83724a183351)) 630 | - Fix spelling mistake. ([`c3c9ee2`](https://github.com/Hpmason/retour-rs/commit/c3c9ee2075fda4169dd5a680ffceeb1d355fcd33)) 631 |
632 | 633 | ## 0.1.1 (2017-06-22) 634 | 635 | 636 | 637 | ### Documentation 638 | 639 | - add badges to Cargo 640 | 641 | ### Bug Fixes 642 | 643 | - warning on 32-bit 644 | - use volatile cell to prevent inlining 645 | 646 | ### Refactor 647 | 648 | - variant → detour, x86 → arch/x86 649 | 650 | ### Commit Statistics 651 | 652 | 653 | 654 | - 4 commits contributed to the release. 655 | - 4 days passed between releases. 656 | - 4 commits were understood as [conventional](https://www.conventionalcommits.org). 657 | - 0 issues like '(#ID)' were seen in commit messages 658 | 659 | ### Commit Details 660 | 661 | 662 | 663 |
view details 664 | 665 | * **Uncategorized** 666 | - Add badges to Cargo ([`5f0583b`](https://github.com/Hpmason/retour-rs/commit/5f0583b679eeefa213482ed229a6e3ae9b8739e2)) 667 | - Warning on 32-bit ([`ff37447`](https://github.com/Hpmason/retour-rs/commit/ff374474f362e4b915aea02a04716c23e922af1c)) 668 | - Use volatile cell to prevent inlining ([`46ed445`](https://github.com/Hpmason/retour-rs/commit/46ed445083097be6277805222c1f4d6593e1f940)) 669 | - Variant → detour, x86 → arch/x86 ([`df1764b`](https://github.com/Hpmason/retour-rs/commit/df1764b6f5efda3a25b71935bd929e0b2e79dfba)) 670 |
671 | 672 | ## v0.1.0 (2017-06-17) 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | ### Chore 714 | 715 | - update version 0.7.1 → 0.8.0 716 | - add linux musl & i686-gnu targets 717 | - add custom macOS linker for RWX execution 718 | - add CI for rust stable 719 | - use latest macOS iamge (10.15) 720 | - disable 'i686-apple-darwin' CI 721 | Since macOS x86 is now a tier 3 target, the advantages of supporting 722 | this target has greatly diminished. For now, it's CI suite will be 723 | disabled. 724 | - update 'cfg-if' dependency 725 | - use shield badges for Azure 726 | - simplify target configuration 727 | - update dependencies 728 | - update version 0.7.0 → 0.7.1 729 | - use azure for all CI 730 | - add missing 32-bit libs 731 | - add azure pipelines configuration 732 | - update version 6.0 → 7.0 733 | - update version 0.5 → 0.6 734 | This version greatly decreases the compile time and total number of 735 | required dependencies. 736 | - update dependencies 737 | - add windows (GNU) to travis config 738 | - update editorconfig; matches rustfmt.toml 739 | - add continous integration 740 | 741 | ### Documentation 742 | 743 | - stylize caption 744 | - add 'MessageBoxW' detour example 745 | - update comments 746 | - update macro/detour comments 747 | - add distinction between travis/appveyor 748 | - add badges to Cargo 749 | - mention type of detour 750 | - add special mention 751 | - fix styling 752 | - fix appendix anchor 753 | - add readme appendix 754 | 755 | ### New Features 756 | 757 | - add support for scoped visibility 758 | - test latest udis86 759 | - update dependencies 760 | - return '&self' from 'initialize' 761 | - migrate to '2018' edition 762 | - implement std 'Error' trait 763 | - update version → 0.5 764 | - update version → 0.4.1 765 | - add clippy config 766 | - add rustfmt config 767 | - update version → 0.4 768 | - update dependencies 769 | - improve architecture abstraction. 770 | This change includes a ton of refactoring that should have been split 771 | into multiple atomic commits. The architecture specific code has a much 772 | improved abstraction whilst the nightly feature is no longer required. 773 | - update version 0.2 → 0.3 774 | - update version 0.1.1 → 0.2 775 | - update dependency versions. 776 | Also remove backtrace from `error-chain` for XP support. 777 | - implement proper static detours 778 | - split InlineDetour → RawDetour/GenericDetour 779 | - implement static detours 780 | - update slice-pool → 0.3.4 781 | - update dependency versions 782 | - add editorconfig 783 | - refactor file organisation 784 | - localize dependencies 785 | - add test for address range 786 | - only implement inline detour 787 | - implemented memory pool 788 | - add mutex for unique access 789 | - initial commit 790 | 791 | ### Bug Fixes 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | - replace deprecated features 804 | - remove deprecated '--all' flag 805 | - 32-bit range calculation overflow 806 | - replace obsolete 'kernel32-sys' dependency 807 | - use new 'Protection' constants 808 | - replace deprecated 'asm!' 809 | - use the latest published libudis86 810 | - disable GNU targets for azure 811 | - explicitly drop boxed data 812 | - use atomic for detour enabled state 813 | - add missing 'dyn' specifiers 814 | - temporarily remove 'musl' CI tests 815 | Cargo does not enable OS/cfg specific examples, so the 'cdylib' tests 816 | errors when using 'musl' due to not supporting the crate type. 817 | Temporarily remove these targets until a workaround can be implemented. 818 | - use 'dyn' for dynamic dispatches 819 | - remove 'const_fn' thanks to 2018 macros 820 | - remove 'ref' prefix 821 | - iterator for descending regions 822 | - remove 'failure' as dependency 823 | - apply clippy recommendations 824 | - use local Error type instead of failure 825 | - scoping issue with 1.26 nightly 826 | - use wrapping_add in handle_relative_branch 827 | * displacement can be negative in some cases 828 | 829 | ### Refactor 830 | 831 | - use inner functions for tests 832 | - prevent 'FreeRegionIter' from being exposed 833 | - use stabilized range 'contains' 834 | - use 'expect' prior to 'unwrap' 835 | - remove 'tap' dependency 836 | - remove 'Boolinator' dependency 837 | - simplify 'StaticDetour' API 838 | - use 'static ref' instead of 'struct' 839 | - use doc for attribute test 840 | - StaticThunk → FixedThunk 841 | - Closure → Vec for x64 thunks 842 | - variant → detour, x86 → arch/x86 843 | - general improvements 844 | 845 | ### Style 846 | 847 | - apply rustfmt 848 | - apply rustfmt 849 | - normalize macro indentation 850 | - normalize indentation 851 | - format project using rustfmt 852 | 853 | ### Commit Statistics 854 | 855 | 856 | 857 | - 24 commits contributed to the release. 858 | - 24 commits were understood as [conventional](https://www.conventionalcommits.org). 859 | - 0 issues like '(#ID)' were seen in commit messages 860 | 861 | ### Commit Details 862 | 863 | 864 | 865 |
view details 866 | 867 | * **Uncategorized** 868 | - Mention type of detour ([`39d9112`](https://github.com/Hpmason/retour-rs/commit/39d9112dfa2066fdc3e99f644ff8cd1dfa355cc9)) 869 | - Add special mention ([`f9df60a`](https://github.com/Hpmason/retour-rs/commit/f9df60ab26746621987779c4dafd51a6b55c7302)) 870 | - Use mmap-fixed from crates.io ([`0c7e273`](https://github.com/Hpmason/retour-rs/commit/0c7e273b18fec764c8c1bfaa30d0e35fcc643b77)) 871 | - Detour local functions ([`a0b23c2`](https://github.com/Hpmason/retour-rs/commit/a0b23c2a68f6f5dc4d617f8262962a0521218bd2)) 872 | - Implement proper static detours ([`772e785`](https://github.com/Hpmason/retour-rs/commit/772e7851f337624d674a1e35f0c905ad9177593c)) 873 | - Split InlineDetour → RawDetour/GenericDetour ([`6c3aa72`](https://github.com/Hpmason/retour-rs/commit/6c3aa72493810974c0b19bf82b3d8f0981979d2e)) 874 | - Implement static detours ([`5d43691`](https://github.com/Hpmason/retour-rs/commit/5d43691730cbba33c1b21e1120d6022260d2e68b)) 875 | - General improvements ([`ae44d79`](https://github.com/Hpmason/retour-rs/commit/ae44d79f9179cb858272024ea2d19f929e1920f7)) 876 | - Update slice-pool → 0.3.4 ([`c51d3f3`](https://github.com/Hpmason/retour-rs/commit/c51d3f3c0d6ce6ef45b87c7971c7210eb49e9fab)) 877 | - Add unreachable message ([`4224d1a`](https://github.com/Hpmason/retour-rs/commit/4224d1a9e8e2f9d454a9cf65cc98a3c80260b65c)) 878 | - Tests in release mode ([`ff4e549`](https://github.com/Hpmason/retour-rs/commit/ff4e54958e0c92ea1720e7f0dd2516068a87b486)) 879 | - Add continous integration ([`f9a24d6`](https://github.com/Hpmason/retour-rs/commit/f9a24d61af4313f5003def3661da8798f02f1666)) 880 | - Fix styling ([`0afcba9`](https://github.com/Hpmason/retour-rs/commit/0afcba9f8efcd4288573dce359ae5a90967acbb8)) 881 | - Fix appendix anchor ([`e932129`](https://github.com/Hpmason/retour-rs/commit/e932129eb635b357d99a39e82835d085677205fe)) 882 | - Update dependency versions ([`3894889`](https://github.com/Hpmason/retour-rs/commit/38948894dd8028c253441212b0dd762927d48698)) 883 | - Add readme appendix ([`7ad7917`](https://github.com/Hpmason/retour-rs/commit/7ad791721535e64acb37cffaf6ccedc2e9db3b3d)) 884 | - Add editorconfig ([`d95ce51`](https://github.com/Hpmason/retour-rs/commit/d95ce516d9bde060704bd98d4bc280c6f8555ec7)) 885 | - Refactor file organisation ([`c2f9fd4`](https://github.com/Hpmason/retour-rs/commit/c2f9fd41450d101499a1ab0f705bcc707c0e2995)) 886 | - Localize dependencies ([`63bc690`](https://github.com/Hpmason/retour-rs/commit/63bc6907b5003de237a86934f18c441763aa3875)) 887 | - Add test for address range ([`68ead08`](https://github.com/Hpmason/retour-rs/commit/68ead08200da0c72c222ea307f4b670e4d2f0f90)) 888 | - Only implement inline detour ([`adc50ed`](https://github.com/Hpmason/retour-rs/commit/adc50ed0752d9947a64eea763713445bb9480ae0)) 889 | - Implemented memory pool ([`d3713e0`](https://github.com/Hpmason/retour-rs/commit/d3713e0fd1b54ca43fe3a3db0a59c899b0321671)) 890 | - Add mutex for unique access ([`0b5fd5f`](https://github.com/Hpmason/retour-rs/commit/0b5fd5fdc47313ca92828a0fa1a13a13d6956127)) 891 | - Initial commit ([`668c73c`](https://github.com/Hpmason/retour-rs/commit/668c73c2ff32e3184ed18aefe967122a399e08a0)) 892 |
893 | 894 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Mason Ginter ", "Elliott Linder "] 3 | description = "A cross-platform detour library written in Rust" 4 | documentation = "https://docs.rs/retour" 5 | homepage = "https://github.com/Hpmason/retour-rs" 6 | keywords = ["detour", "hook", "function", "api", "redirect"] 7 | license = "BSD-2-Clause" 8 | name = "retour" 9 | readme = "README.md" 10 | repository = "https://github.com/Hpmason/retour-rs" 11 | version = "0.4.0-alpha.3" 12 | edition = "2018" 13 | rust-version = "1.60.0" 14 | 15 | # [badges] 16 | # azure-devops = { project = "darfink/detour-rs", pipeline = "darfink.detour-rs" } 17 | 18 | [dependencies] 19 | cfg-if = "1.0.0" 20 | generic-array = "0.14.7" 21 | once_cell = "1.18.0" 22 | libc = "0.2.145" 23 | mmap = { package = "mmap-fixed-fixed", version = "0.1.3" } 24 | region = "3.0.0" 25 | slice-pool = {package = "slice-pool2", version = "0.4.3" } 26 | 27 | [dev-dependencies] 28 | matches = "0.1.10" 29 | ctor = "0.2.2" 30 | 31 | [features] 32 | default = [] 33 | nightly = [] 34 | thiscall-abi = ["nightly"] 35 | static-detour = ["nightly"] 36 | 28-args = [] 37 | 42-args = ["28-args"] 38 | 39 | [[example]] 40 | name = "messageboxw_detour" 41 | required-features = ["static-detour"] 42 | crate-type = ["cdylib"] 43 | 44 | [[example]] 45 | name = "cat_detour" 46 | required-features = ["static-detour"] 47 | crate-type = ["cdylib"] 48 | 49 | [[example]] 50 | name = "com_dxgi_present" 51 | required-features = ["static-detour"] 52 | crate-type = ["cdylib"] 53 | 54 | [[example]] 55 | name = "kernel32_detour" 56 | crate-type = ["cdylib"] 57 | 58 | [target."cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))".dependencies.iced-x86] 59 | version = "1.20" 60 | default-features = false 61 | # https://github.com/icedland/iced/blob/master/src/rust/iced-x86/README.md#crate-feature-flags 62 | features = ["std", "decoder", "fast_fmt"] 63 | 64 | [target."cfg(windows)".dev-dependencies.windows] 65 | version = "0.48" 66 | features = ["Win32_Foundation", 67 | "Win32_System_Console", "Win32_System_LibraryLoader", "Win32_System_SystemServices", 68 | "Win32_Graphics_Gdi", "Win32_Graphics_Direct3D", "Win32_Graphics_Direct3D11", 69 | "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", 70 | "Win32_UI_WindowsAndMessaging", 71 | ] 72 | 73 | [package.metadata.docs.rs] 74 | all-features = true 75 | rustdoc-args = ["--cfg", "docsrs"] 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | detour-rs - A cross-platform detour library written in Rust 2 | Copyright (C) 2017 Elliott Linder. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 19 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | =============================================================================== 28 | 29 | minhook-rs - A minimalist x86/x86-64 hooking library for Rust 30 | Copyright (C) 2015 Jascha Neutelings. 31 | All rights reserved. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions 35 | are met: 36 | 37 | 1. Redistributions of source code must retain the above copyright 38 | notice, this list of conditions and the following disclaimer. 39 | 2. Redistributions in binary form must reproduce the above copyright 40 | notice, this list of conditions and the following disclaimer in the 41 | documentation and/or other materials provided with the distribution. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 45 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 46 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 47 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 48 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 49 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 50 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 51 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 52 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 53 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # `retour` (a `detour` Fork) 4 | 5 | [![Crates.io][crates-badge]][crates-url] 6 | [![docs.rs][docs-badge]][docs-url] 7 | [![Lcense][license-badge]][license-url] 8 | [![Cargo Check/Tests][actions-badge]][actions-url] 9 |
10 | 11 | [crates-badge]: https://img.shields.io/crates/v/retour.svg 12 | [crates-url]: https://crates.io/crates/retour 13 | 14 | [docs-badge]: https://docs.rs/retour/badge.svg 15 | [docs-url]: https://docs.rs/retour 16 | 17 | [license-badge]: https://img.shields.io/crates/l/retour 18 | [license-url]: ./LICENSE 19 | 20 | [actions-badge]: https://github.com/Hpmason/retour-rs/actions/workflows/ci.yml/badge.svg 21 | [actions-url]: https://github.com/Hpmason/retour-rs/actions/workflows/ci.yml 22 | 23 | (Fork of original [detour-rs](https://github.com/darfink/detour-rs) 24 | that works on nightly after nightly-2022-11-07) 25 | 26 | 27 | This is a cross-platform detour library developed in Rust. Beyond the basic 28 | functionality, this library handles branch redirects, RIP-relative 29 | instructions, hot-patching, NOP-padded functions, and allows the original 30 | function to be called using a trampoline whilst hooked. 31 | 32 | This is one of few **cross-platform** detour libraries that exists, and to 33 | maintain this feature, not all desired functionality can be supported due to 34 | lack of cross-platform APIs. Therefore [EIP relocation](#appendix) is not 35 | supported. 36 | 37 | **NOTE**: Nightly is currently required for `static_detour!` and is enabled with the `static-detour` feature flag. 38 | 39 | ## Platforms 40 | 41 | This library provides CI for these targets: 42 | 43 | - Linux 44 | * `i686-unknown-linux-gnu` 45 | * `x86_64-unknown-linux-gnu` 46 | * `x86_64-unknown-linux-musl` 47 | - Windows 48 | * `i686-pc-windows-gnu` 49 | * `i686-pc-windows-msvc` 50 | * `x86_64-pc-windows-gnu` 51 | * `x86_64-pc-windows-msvc` 52 | - macOS 53 | * ~~`i686-apple-darwin`~~ 54 | * `x86_64-apple-darwin` 55 | 56 | ## Installation 57 | 58 | Add this to your `Cargo.toml`: 59 | 60 | ```toml 61 | [dependencies] 62 | # `static-detour` feature requires nightly 63 | retour = { version = "0.3", features = ["static-detour"] } 64 | ``` 65 | 66 | ## Supported Versions 67 | This crate, with default features, will support the MSRV in `Cargo.toml` 68 | (currently 1.60.0). Certain features may require newer versions of the compiler, 69 | which will be documented here and in the docs. Any features that require the 70 | nightly compiler will always target the newest version. 71 | 72 | Feature versions: 73 | - `static-detour`: nightly 74 | - `thiscall-abi`: 1.73.0 or newer 75 | 76 | ## Example 77 | 78 | - A static detour (one of *three* different detours): 79 | 80 | ```rust 81 | use std::error::Error; 82 | use retour::static_detour; 83 | 84 | static_detour! { 85 | static Test: /* extern "X" */ fn(i32) -> i32; 86 | } 87 | 88 | fn add5(val: i32) -> i32 { 89 | val + 5 90 | } 91 | 92 | fn add10(val: i32) -> i32 { 93 | val + 10 94 | } 95 | 96 | fn main() -> Result<(), Box> { 97 | // Reroute the 'add5' function to 'add10' (can also be a closure) 98 | unsafe { Test.initialize(add5, add10)? }; 99 | 100 | assert_eq!(add5(1), 6); 101 | assert_eq!(Test.call(1), 6); 102 | 103 | // Hooks must be enabled to take effect 104 | unsafe { Test.enable()? }; 105 | 106 | // The original function is detoured to 'add10' 107 | assert_eq!(add5(1), 11); 108 | 109 | // The original function can still be invoked using 'call' 110 | assert_eq!(Test.call(1), 6); 111 | 112 | // It is also possible to change the detour whilst hooked 113 | Test.set_detour(|val| val - 5); 114 | assert_eq!(add5(5), 0); 115 | 116 | unsafe { Test.disable()? }; 117 | 118 | assert_eq!(add5(1), 6); 119 | Ok(()) 120 | } 121 | ``` 122 | 123 | - A Windows API hooking example is available [here](./examples/messageboxw_detour.rs); build it by running: 124 | ``` 125 | $ cargo +nightly build --features="static-detour" --example messageboxw_detour 126 | ``` 127 | 128 | - A non-nightly example using GenericDetour can be found [here](./examples/kernel32_detour.rs); build it by running: 129 | ``` 130 | $ cargo build --example kernel32_detour 131 | ``` 132 | 133 | ## Mentions 134 | 135 | This is fork of the original [detour-rs][detour-rs] creator 136 | [darfink][detour-rs-author] that put *so much* work into the original crate. 137 | 138 | Part of the library's external user interface was inspired by 139 | [minhook-rs][minhook], created by [Jascha-N][minhook-author], and it contains 140 | derivative code of his work. 141 | 142 | ## Injection Methods 143 | This crate provides the ability to hook functions in other applications, but does not provide the utilities necessary for injecting/attaching to another process. If you're looking for ways to inject your hooking library here are some approaches you can look into: 144 | 145 | - dll injection libraries such as [dll-syringe](https://crates.io/crates/dll-syringe) 146 | - LD_PRELOAD on *nix platforms 147 | - Various debuggers, including x64dbg and Cheat Engine 148 | 149 | ## Appendix 150 | 151 | - *EIP relocation* 152 | 153 | *Should be performed whenever a function's prolog instructions 154 | are being executed, simultaneously as the function itself is being 155 | detoured. This is done by halting all affected threads, copying the affected 156 | instructions and appending a `JMP` to return to the function. This is 157 | barely ever an issue, and never in single-threaded environments, but YMMV.* 158 | 159 | - *NOP-padding* 160 | ```c 161 | int function() { return 0; } 162 | // xor eax, eax 163 | // ret 164 | // nop 165 | // nop 166 | // ... 167 | ``` 168 | *Functions such as this one, lacking a hot-patching area, and too small to 169 | be hooked with a 5-byte `jmp`, are supported thanks to the detection of 170 | code padding (`NOP/INT3` instructions). Therefore the required amount of 171 | trailing `NOP` instructions will be replaced, to make room for the detour.* 172 | 173 | 174 | [minhook-author]: https://github.com/Jascha-N 175 | [minhook]: https://github.com/Jascha-N/minhook-rs/ 176 | [detour-rs]: https://github.com/darfink/detour-rs 177 | [detour-rs-author]: https://github.com/darfink 178 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | too-many-arguments-threshold = 15 2 | -------------------------------------------------------------------------------- /examples/cat_detour.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(not(windows), feature = "static-detour"))] 2 | 3 | use retour::static_detour; 4 | use std::ffi::CString; 5 | use std::os::raw::c_char; 6 | use std::os::raw::c_int; 7 | 8 | extern "C" { 9 | fn open(pathname: *const c_char, flags: c_int) -> c_int; 10 | } 11 | 12 | static_detour! { 13 | static Opentour: unsafe extern "C" fn(*const c_char, c_int) -> c_int; 14 | } 15 | 16 | fn definitely_open(_: *const c_char, _: c_int) -> c_int { 17 | let cstring = CString::new("/etc/timezone").unwrap(); 18 | let fd = unsafe { Opentour.call(cstring.as_ptr() as *const c_char, 0) }; 19 | assert!(fd > 0); 20 | fd 21 | } 22 | 23 | #[ctor::ctor] 24 | fn main() { 25 | unsafe { 26 | Opentour.initialize(open, definitely_open).unwrap(); 27 | Opentour.enable().unwrap(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/com_dxgi_present.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(windows, feature = "static-detour"))] 2 | //! A `IDXGISwapChain::Present` detour example. 3 | //! 4 | //! Ensure the crate is compiled as a 'cdylib' library to allow C interop. 5 | use std::error::Error; 6 | use std::mem::size_of; 7 | use std::os::raw::c_void; 8 | use std::ptr::null; 9 | 10 | use windows::core::{Interface, HRESULT, HSTRING, PCWSTR}; 11 | use windows::Win32::Foundation::{GetLastError, BOOL, HMODULE, HWND, LPARAM, LRESULT, WPARAM}; 12 | use windows::Win32::Graphics::Direct3D::*; 13 | use windows::Win32::Graphics::Direct3D11::*; 14 | use windows::Win32::Graphics::Dxgi::Common::*; 15 | use windows::Win32::Graphics::Dxgi::*; 16 | use windows::Win32::Graphics::Gdi::HBRUSH; 17 | use windows::Win32::System::Console::AllocConsole; 18 | use windows::Win32::System::LibraryLoader::{DisableThreadLibraryCalls, GetModuleHandleW}; 19 | use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH; 20 | use windows::Win32::UI::WindowsAndMessaging::*; 21 | 22 | use retour::static_detour; 23 | 24 | static_detour! { 25 | static PresentHook: unsafe extern "system" fn(*mut c_void, u32, u32) -> HRESULT; 26 | } 27 | 28 | #[allow(non_snake_case)] 29 | fn present(This: *mut c_void, SyncInterval: u32, Flags: u32) -> HRESULT { 30 | println!("present"); 31 | unsafe { PresentHook.call(This, SyncInterval, Flags) } 32 | } 33 | 34 | #[no_mangle] 35 | #[allow(non_snake_case)] 36 | pub extern "system" fn DllMain(module: HMODULE, call_reason: u32, _reserved: *mut c_void) -> BOOL { 37 | unsafe { 38 | DisableThreadLibraryCalls(module); 39 | } 40 | 41 | if call_reason == DLL_PROCESS_ATTACH { 42 | unsafe { 43 | AllocConsole(); 44 | } 45 | 46 | std::thread::spawn(|| unsafe { 47 | match crate::main() { 48 | Ok(()) => 0 as u32, 49 | Err(e) => { 50 | println!("Error occurred when injecting: {}", e); 51 | 1 52 | }, 53 | } 54 | }); 55 | } 56 | true.into() 57 | } 58 | 59 | unsafe extern "system" fn window_proc( 60 | hwnd: HWND, 61 | msg: u32, 62 | w_param: WPARAM, 63 | l_param: LPARAM, 64 | ) -> LRESULT { 65 | // workaround for https://github.com/microsoft/windows-rs/issues/2556 66 | DefWindowProcW(hwnd, msg, w_param, l_param) 67 | } 68 | 69 | unsafe fn main() -> Result<(), Box> { 70 | let vtable = get_d3d11_vtables().as_ref().unwrap(); 71 | println!("Found Present Pointer at {:p}", vtable.Present as *const ()); 72 | PresentHook.initialize(vtable.Present, present)?; 73 | 74 | PresentHook.enable()?; 75 | 76 | println!("Hook activated"); 77 | Ok(()) 78 | } 79 | 80 | unsafe fn get_render_window() -> (WNDCLASSEXW, HWND) { 81 | let window_class_name = HSTRING::from("DxHookWindowClass"); 82 | let window_class = WNDCLASSEXW { 83 | cbSize: size_of::() as u32, 84 | style: CS_HREDRAW | CS_VREDRAW, 85 | lpfnWndProc: Some(window_proc), 86 | cbClsExtra: 0, 87 | cbWndExtra: 0, 88 | hInstance: GetModuleHandleW(None).unwrap(), 89 | hIcon: HICON::default(), 90 | hCursor: HCURSOR::default(), 91 | hbrBackground: HBRUSH::default(), 92 | lpszMenuName: PCWSTR(null()), 93 | lpszClassName: PCWSTR(window_class_name.as_wide().as_ptr()), 94 | hIconSm: HICON::default(), 95 | }; 96 | 97 | RegisterClassExW(&window_class); 98 | 99 | let hwnd = CreateWindowExW( 100 | WINDOW_EX_STYLE::default(), 101 | window_class.lpszClassName, 102 | PCWSTR(HSTRING::from("DxHookWindowClass").as_wide().as_ptr()), 103 | WS_OVERLAPPEDWINDOW, 104 | 0, 105 | 0, 106 | 100, 107 | 100, 108 | HWND::default(), 109 | HMENU::default(), 110 | window_class.hInstance, 111 | None, 112 | ); 113 | 114 | (window_class, hwnd) 115 | } 116 | 117 | unsafe fn get_d3d11_vtables() -> *const IDXGISwapChain_Vtbl { 118 | let (window_class, hwnd) = get_render_window(); 119 | println!("made new hwnd {:?}", hwnd); 120 | let swapchain_desc = DXGI_SWAP_CHAIN_DESC { 121 | BufferDesc: DXGI_MODE_DESC { 122 | Width: 100, 123 | Height: 100, 124 | RefreshRate: DXGI_RATIONAL { 125 | Numerator: 60, 126 | Denominator: 1, 127 | }, 128 | Format: DXGI_FORMAT_R8G8B8A8_UNORM, 129 | ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, 130 | Scaling: DXGI_MODE_SCALING_UNSPECIFIED, 131 | }, 132 | SampleDesc: DXGI_SAMPLE_DESC { 133 | Count: 1, 134 | Quality: 0, 135 | }, 136 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, 137 | BufferCount: 1, 138 | OutputWindow: hwnd, 139 | Windowed: true.into(), 140 | SwapEffect: DXGI_SWAP_EFFECT_DISCARD, 141 | Flags: DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.0 as u32, 142 | }; 143 | 144 | let feature_levels = [D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1]; 145 | let mut out_swapchain = None; 146 | let mut out_device = None; 147 | let mut out_context: Option = None; 148 | // 149 | D3D11CreateDeviceAndSwapChain( 150 | None, 151 | D3D_DRIVER_TYPE_HARDWARE, 152 | HMODULE::default(), 153 | D3D11_CREATE_DEVICE_FLAG::default(), 154 | Some(&feature_levels), 155 | D3D11_SDK_VERSION, 156 | Some(&swapchain_desc), 157 | Some(&mut out_swapchain), 158 | Some(&mut out_device), 159 | None, 160 | Some(&mut out_context), 161 | ) 162 | .unwrap(); 163 | println!("d3dhresult {:x?}", 0); 164 | 165 | let swapchain = out_swapchain.unwrap(); 166 | let swapchain_vtbl: &IDXGISwapChain_Vtbl = swapchain.vtable(); 167 | 168 | if !CloseWindow(hwnd).as_bool() { 169 | println!("Failed to close window. Error: {:?}", GetLastError()); 170 | } 171 | if !DestroyWindow(hwnd).as_bool() { 172 | println!("Failed to destroy window. Error: {:?}", GetLastError()); 173 | } 174 | // Needs fresh pointer to class name (else error 1411) + call DestroyWindow first (else error 1412) 175 | if !UnregisterClassW(PCWSTR(HSTRING::from("DxHookWindowClass").as_wide().as_ptr()), window_class.hInstance).as_bool() { 176 | println!("Failed to unregister window class. Error: {:?}", GetLastError()); 177 | } 178 | 179 | swapchain_vtbl 180 | } 181 | -------------------------------------------------------------------------------- /examples/kernel32_detour.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | #![allow(non_upper_case_globals, non_snake_case, non_camel_case_types)] 3 | 4 | use std::{ffi::CStr, os::raw::c_void}; 5 | 6 | use once_cell::sync::Lazy; 7 | use retour::GenericDetour; 8 | use windows::{ 9 | core::PCSTR, 10 | Win32::{ 11 | Foundation::{BOOL, HANDLE, HMODULE}, 12 | System::{ 13 | LibraryLoader::{GetProcAddress, LoadLibraryA}, 14 | SystemServices::{ 15 | DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH, 16 | }, 17 | }, 18 | }, 19 | }; 20 | type fn_LoadLibraryA = extern "system" fn(PCSTR) -> HMODULE; 21 | 22 | static hook_LoadLibraryA: Lazy> = Lazy::new(|| { 23 | let library_handle = unsafe { LoadLibraryA(PCSTR(b"kernel32.dll\0".as_ptr() as _)) }.unwrap(); 24 | let address = unsafe { GetProcAddress(library_handle, PCSTR(b"LoadLibraryA\0".as_ptr() as _)) }; 25 | let ori: fn_LoadLibraryA = unsafe { std::mem::transmute(address) }; 26 | return unsafe { GenericDetour::new(ori, our_LoadLibraryA).unwrap() }; 27 | }); 28 | 29 | extern "system" fn our_LoadLibraryA(lpFileName: PCSTR) -> HMODULE { 30 | let file_name = unsafe { CStr::from_ptr(lpFileName.as_ptr() as _) }; 31 | println!("our_LoadLibraryA lpFileName = {:?}", file_name); 32 | unsafe { hook_LoadLibraryA.disable().unwrap() }; 33 | let ret_val = hook_LoadLibraryA.call(lpFileName); 34 | println!( 35 | "our_LoadLibraryA lpFileName = {:?} ret_val = {:#X}", 36 | file_name, ret_val.0 37 | ); 38 | unsafe { hook_LoadLibraryA.enable().unwrap() }; 39 | return ret_val; 40 | } 41 | 42 | #[no_mangle] 43 | unsafe extern "system" fn DllMain(_hinst: HANDLE, reason: u32, _reserved: *mut c_void) -> BOOL { 44 | match reason { 45 | DLL_PROCESS_ATTACH => { 46 | println!("attaching"); 47 | unsafe { 48 | hook_LoadLibraryA.enable().unwrap(); 49 | } 50 | }, 51 | DLL_PROCESS_DETACH => { 52 | println!("detaching"); 53 | }, 54 | DLL_THREAD_ATTACH => {}, 55 | DLL_THREAD_DETACH => {}, 56 | _ => {}, 57 | }; 58 | return BOOL::from(true); 59 | } 60 | -------------------------------------------------------------------------------- /examples/messageboxw_detour.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(windows, feature = "static-detour"))] 2 | //! A `MessageBoxW` detour example. 3 | //! 4 | //! Ensure the crate is compiled as a 'cdylib' library to allow C interop. 5 | use retour::static_detour; 6 | use std::error::Error; 7 | use std::ffi::c_int; 8 | use std::os::raw::c_void; 9 | use std::{ffi::CString, iter, mem}; 10 | use windows::core::{PCSTR, PCWSTR}; 11 | use windows::w; 12 | use windows::Win32::Foundation::{BOOL, HANDLE, HWND}; 13 | use windows::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}; 14 | use windows::Win32::System::SystemServices::{ 15 | DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH, 16 | }; 17 | 18 | static_detour! { 19 | static MessageBoxWHook: unsafe extern "system" fn(HWND, PCWSTR, PCWSTR, u32) -> c_int; 20 | } 21 | 22 | // A type alias for `MessageBoxW` (makes the transmute easy on the eyes) 23 | type FnMessageBoxW = unsafe extern "system" fn(HWND, PCWSTR, PCWSTR, u32) -> c_int; 24 | 25 | /// Called when the DLL is attached to the process. 26 | unsafe fn main() -> Result<(), Box> { 27 | // Retrieve an absolute address of `MessageBoxW`. This is required for 28 | // libraries due to the import address table. If `MessageBoxW` would be 29 | // provided directly as the target, it would only hook this DLL's 30 | // `MessageBoxW`. Using the method below an absolute address is retrieved 31 | // instead, detouring all invocations of `MessageBoxW` in the active process. 32 | let address = get_module_symbol_address("user32.dll", "MessageBoxW") 33 | .expect("could not find 'MessageBoxW' address"); 34 | let target: FnMessageBoxW = mem::transmute(address); 35 | 36 | // Initialize AND enable the detour (the 2nd parameter can also be a closure) 37 | MessageBoxWHook 38 | .initialize(target, messageboxw_detour)? 39 | .enable()?; 40 | Ok(()) 41 | } 42 | 43 | /// Called whenever `MessageBoxW` is invoked in the process. 44 | fn messageboxw_detour(hwnd: HWND, text: PCWSTR, _caption: PCWSTR, msgbox_style: u32) -> c_int { 45 | // Call the original `MessageBoxW`, but replace the caption 46 | let replaced_caption = w!("Detoured!"); 47 | unsafe { MessageBoxWHook.call(hwnd, text, replaced_caption, msgbox_style) } 48 | } 49 | 50 | /// Returns a module symbol's absolute address. 51 | fn get_module_symbol_address(module: &str, symbol: &str) -> Option { 52 | let module = module 53 | .encode_utf16() 54 | .chain(iter::once(0)) 55 | .collect::>(); 56 | let symbol = CString::new(symbol).unwrap(); 57 | unsafe { 58 | let handle = GetModuleHandleW(PCWSTR(module.as_ptr() as _)).unwrap(); 59 | match GetProcAddress(handle, PCSTR(symbol.as_ptr() as _)) { 60 | Some(func) => Some(func as usize), 61 | None => None, 62 | } 63 | } 64 | } 65 | 66 | #[no_mangle] 67 | unsafe extern "system" fn DllMain(_hinst: HANDLE, reason: u32, _reserved: *mut c_void) -> BOOL { 68 | match reason { 69 | DLL_PROCESS_ATTACH => { 70 | println!("attaching"); 71 | unsafe { main().unwrap() } 72 | }, 73 | DLL_PROCESS_DETACH => { 74 | println!("detaching"); 75 | }, 76 | DLL_THREAD_ATTACH => {}, 77 | DLL_THREAD_DETACH => {}, 78 | _ => {}, 79 | }; 80 | return BOOL::from(true); 81 | } 82 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | fn_single_line = false 3 | format_strings = true 4 | match_block_trailing_comma = true 5 | normalize_comments = true 6 | reorder_imports = true 7 | tab_spaces = 2 8 | use_field_init_shorthand = true 9 | use_try_shorthand = true 10 | wrap_comments = true 11 | -------------------------------------------------------------------------------- /src/alloc/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | mod proximity; 6 | mod search; 7 | 8 | /// A thread-safe memory pool for allocating chunks close to addresses. 9 | pub struct ThreadAllocator(Arc>); 10 | 11 | // TODO: Decrease use of mutexes 12 | impl ThreadAllocator { 13 | /// Creates a new proximity memory allocator. 14 | pub fn new(max_distance: usize) -> Self { 15 | ThreadAllocator(Arc::new(Mutex::new(proximity::ProximityAllocator { 16 | max_distance, 17 | pools: Vec::new(), 18 | }))) 19 | } 20 | 21 | /// Allocates read-, write- & executable memory close to `origin`. 22 | pub fn allocate(&self, origin: *const (), size: usize) -> Result { 23 | let mut allocator = self.0.lock().unwrap(); 24 | allocator 25 | .allocate(origin, size) 26 | .map(|data| ExecutableMemory { 27 | allocator: self.0.clone(), 28 | data, 29 | }) 30 | } 31 | } 32 | 33 | /// A handle for allocated proximity memory. 34 | pub struct ExecutableMemory { 35 | allocator: Arc>, 36 | data: proximity::Allocation, 37 | } 38 | 39 | impl Drop for ExecutableMemory { 40 | fn drop(&mut self) { 41 | // Release the associated memory map (if unique) 42 | self.allocator.lock().unwrap().release(&self.data); 43 | } 44 | } 45 | 46 | impl Deref for ExecutableMemory { 47 | type Target = [u8]; 48 | 49 | fn deref(&self) -> &Self::Target { 50 | self.data.deref() 51 | } 52 | } 53 | 54 | impl DerefMut for ExecutableMemory { 55 | fn deref_mut(&mut self) -> &mut [u8] { 56 | self.data.deref_mut() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/alloc/proximity.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | use std::slice; 3 | 4 | use slice_pool::sync::{SliceBox, SlicePool}; 5 | 6 | use super::search as region_search; 7 | use crate::error::{Error, Result}; 8 | 9 | /// Defines the allocation type. 10 | pub type Allocation = SliceBox; 11 | 12 | /// Shared instance containing all pools 13 | pub struct ProximityAllocator { 14 | pub max_distance: usize, 15 | pub pools: Vec>, 16 | } 17 | 18 | impl ProximityAllocator { 19 | /// Allocates a slice in an eligible memory map. 20 | pub fn allocate(&mut self, origin: *const (), size: usize) -> Result { 21 | let memory_range = ((origin as usize).saturating_sub(self.max_distance)) 22 | ..((origin as usize).saturating_add(self.max_distance)); 23 | 24 | // Check if an existing pool can handle the allocation request 25 | self.allocate_memory(&memory_range, size).or_else(|_| { 26 | // ... otherwise allocate a pool within the memory range 27 | self.allocate_pool(&memory_range, origin, size).map(|pool| { 28 | // Use the newly allocated pool for the request 29 | let allocation = pool.alloc(size).unwrap(); 30 | self.pools.push(pool); 31 | allocation 32 | }) 33 | }) 34 | } 35 | 36 | /// Releases the memory pool associated with an allocation. 37 | pub fn release(&mut self, value: &Allocation) { 38 | // Find the associated memory pool 39 | let index = self 40 | .pools 41 | .iter() 42 | .position(|pool| { 43 | let lower = pool.as_ptr() as usize; 44 | let upper = lower + pool.len(); 45 | 46 | // Determine if this is the associated memory pool 47 | (lower..upper).contains(&(value.as_ptr() as usize)) 48 | }) 49 | .expect("retrieving associated memory pool"); 50 | 51 | // Release the pool if the associated allocation is unique 52 | if self.pools[index].len() == 1 { 53 | self.pools.remove(index); 54 | } 55 | } 56 | 57 | /// Allocates a chunk using any of the existing pools. 58 | fn allocate_memory(&mut self, range: &Range, size: usize) -> Result { 59 | // Returns true if the pool's memory is within the range 60 | let is_pool_in_range = |pool: &SlicePool| { 61 | let lower = pool.as_ptr() as usize; 62 | let upper = lower + pool.len(); 63 | range.contains(&lower) && range.contains(&(upper - 1)) 64 | }; 65 | 66 | // Tries to allocate a slice within any eligible pool 67 | self 68 | .pools 69 | .iter_mut() 70 | .filter_map(|pool| { 71 | if is_pool_in_range(pool) { 72 | pool.alloc(size) 73 | } else { 74 | None 75 | } 76 | }) 77 | .next() 78 | .ok_or(Error::OutOfMemory) 79 | } 80 | 81 | /// Allocates a new pool close to `origin`. 82 | fn allocate_pool( 83 | &mut self, 84 | range: &Range, 85 | origin: *const (), 86 | size: usize, 87 | ) -> Result> { 88 | let before = region_search::before(origin, Some(range.clone())); 89 | let after = region_search::after(origin, Some(range.clone())); 90 | 91 | // TODO: Part of the pool can be out of range 92 | // Try to allocate after the specified address first (mostly because 93 | // macOS cannot allocate memory before the process's address). 94 | after 95 | .chain(before) 96 | .filter_map(|result| match result { 97 | Ok(address) => Self::allocate_fixed_pool(address, size).map(Ok), 98 | Err(error) => Some(Err(error)), 99 | }) 100 | .next() 101 | .unwrap_or(Err(Error::OutOfMemory)) 102 | } 103 | 104 | /// Tries to allocate fixed memory at the specified address. 105 | fn allocate_fixed_pool(address: *const (), size: usize) -> Option> { 106 | // Try to allocate memory at the specified address 107 | mmap::MemoryMap::new( 108 | size, 109 | &[ 110 | mmap::MapOption::MapReadable, 111 | mmap::MapOption::MapWritable, 112 | mmap::MapOption::MapExecutable, 113 | mmap::MapOption::MapAddr(address as *const _), 114 | ], 115 | ) 116 | .ok() 117 | .map(SliceableMemoryMap) 118 | .map(SlicePool::new) 119 | } 120 | } 121 | 122 | // TODO: Use memmap-rs instead 123 | /// A wrapper for making a memory map compatible with `SlicePool`. 124 | struct SliceableMemoryMap(mmap::MemoryMap); 125 | 126 | impl SliceableMemoryMap { 127 | pub fn as_slice(&self) -> &[u8] { 128 | unsafe { slice::from_raw_parts(self.0.data(), self.0.len()) } 129 | } 130 | 131 | pub fn as_mut_slice(&mut self) -> &mut [u8] { 132 | unsafe { slice::from_raw_parts_mut(self.0.data(), self.0.len()) } 133 | } 134 | } 135 | 136 | impl AsRef<[u8]> for SliceableMemoryMap { 137 | fn as_ref(&self) -> &[u8] { 138 | self.as_slice() 139 | } 140 | } 141 | 142 | impl AsMut<[u8]> for SliceableMemoryMap { 143 | fn as_mut(&mut self) -> &mut [u8] { 144 | self.as_mut_slice() 145 | } 146 | } 147 | 148 | unsafe impl Send for SliceableMemoryMap {} 149 | unsafe impl Sync for SliceableMemoryMap {} 150 | -------------------------------------------------------------------------------- /src/alloc/search.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use std::ops::Range; 3 | 4 | /// Returns an iterator for free after the specified address. 5 | pub fn after( 6 | origin: *const (), 7 | range: Option>, 8 | ) -> impl Iterator> { 9 | FreeRegionIter::new(origin, range, SearchDirection::After) 10 | } 11 | 12 | /// Returns an iterator for free before the specified address. 13 | pub fn before( 14 | origin: *const (), 15 | range: Option>, 16 | ) -> impl Iterator> { 17 | FreeRegionIter::new(origin, range, SearchDirection::Before) 18 | } 19 | 20 | /// Direction for the region search. 21 | enum SearchDirection { 22 | Before, 23 | After, 24 | } 25 | 26 | /// An iterator searching for free regions. 27 | struct FreeRegionIter { 28 | range: Range, 29 | search: SearchDirection, 30 | current: usize, 31 | } 32 | 33 | impl FreeRegionIter { 34 | /// Creates a new iterator for free regions. 35 | fn new(origin: *const (), range: Option>, search: SearchDirection) -> Self { 36 | FreeRegionIter { 37 | range: range.unwrap_or(0..usize::max_value()), 38 | current: origin as usize, 39 | search, 40 | } 41 | } 42 | } 43 | 44 | impl Iterator for FreeRegionIter { 45 | type Item = Result<*const ()>; 46 | 47 | /// Returns the closest free region for the current address. 48 | fn next(&mut self) -> Option { 49 | let page_size = region::page::size(); 50 | 51 | while self.current > 0 && self.range.contains(&self.current) { 52 | match region::query(self.current as *const usize) { 53 | Ok(region) => { 54 | let range = region.as_range(); 55 | self.current = match self.search { 56 | SearchDirection::Before => range.start.saturating_sub(page_size), 57 | SearchDirection::After => range.end, 58 | } 59 | }, 60 | Err(error) => { 61 | // Check whether the region is free, otherwise return the error 62 | let result = Some(match error { 63 | region::Error::UnmappedRegion => Ok(self.current as *const _), 64 | inner => Err(Error::RegionFailure(inner)), 65 | }); 66 | 67 | // Adjust the offset for repeated calls. 68 | self.current = match self.search { 69 | SearchDirection::Before => self.current.saturating_sub(page_size), 70 | SearchDirection::After => self.current + page_size, 71 | }; 72 | 73 | return result; 74 | }, 75 | } 76 | } 77 | 78 | None 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/arch/detour.rs: -------------------------------------------------------------------------------- 1 | use super::memory; 2 | use crate::error::{Error, Result}; 3 | use crate::{alloc, arch, util}; 4 | use std::cell::UnsafeCell; 5 | use std::fmt; 6 | use std::sync::atomic::{AtomicBool, Ordering}; 7 | 8 | /// An architecture-independent implementation of a base detour. 9 | /// 10 | /// This class is never instantiated by itself, it merely exposes an API 11 | /// available through it's descendants. 12 | pub struct Detour { 13 | #[allow(dead_code)] 14 | relay: Option, 15 | trampoline: alloc::ExecutableMemory, 16 | patcher: UnsafeCell, 17 | enabled: AtomicBool, 18 | } 19 | 20 | impl Detour { 21 | pub unsafe fn new(target: *const (), detour: *const ()) -> Result { 22 | if target == detour { 23 | Err(Error::SameAddress)?; 24 | } 25 | 26 | // Lock this so OS operations are not performed in parallell 27 | let mut pool = memory::POOL.lock().unwrap(); 28 | 29 | if !util::is_executable_address(target)? || !util::is_executable_address(detour)? { 30 | Err(Error::NotExecutable)?; 31 | } 32 | 33 | // Create a trampoline generator for the target function 34 | let margin = arch::meta::prolog_margin(target); 35 | let trampoline = arch::Trampoline::new(target, margin)?; 36 | 37 | // A relay is used in case a normal branch cannot reach the destination 38 | let relay = if let Some(emitter) = arch::meta::relay_builder(target, detour)? { 39 | Some(memory::allocate_pic(&mut pool, &emitter, target)?) 40 | } else { 41 | None 42 | }; 43 | 44 | // If a relay is supplied, use it instead of the detour address 45 | let detour = relay 46 | .as_ref() 47 | .map(|code| code.as_ptr() as *const ()) 48 | .unwrap_or(detour); 49 | 50 | Ok(Detour { 51 | patcher: UnsafeCell::new(arch::Patcher::new( 52 | target, 53 | detour, 54 | trampoline.prolog_size(), 55 | )?), 56 | trampoline: memory::allocate_pic(&mut pool, trampoline.emitter(), target)?, 57 | enabled: AtomicBool::default(), 58 | relay, 59 | }) 60 | } 61 | 62 | /// Enables the detour. 63 | pub unsafe fn enable(&self) -> Result<()> { 64 | self.toggle(true) 65 | } 66 | 67 | /// Disables the detour. 68 | pub unsafe fn disable(&self) -> Result<()> { 69 | self.toggle(false) 70 | } 71 | 72 | /// Returns whether the detour is enabled or not. 73 | pub fn is_enabled(&self) -> bool { 74 | self.enabled.load(Ordering::SeqCst) 75 | } 76 | 77 | /// Returns a reference to the generated trampoline. 78 | pub fn trampoline(&self) -> &() { 79 | unsafe { 80 | (self.trampoline.as_ptr() as *const ()) 81 | .as_ref() 82 | .expect("trampoline should not be null") 83 | } 84 | } 85 | 86 | /// Enables or disables the detour. 87 | unsafe fn toggle(&self, enabled: bool) -> Result<()> { 88 | let _guard = memory::POOL.lock().unwrap(); 89 | 90 | if self.enabled.load(Ordering::SeqCst) == enabled { 91 | return Ok(()); 92 | } 93 | 94 | // Runtime code is by default only read-execute 95 | let _handle = { 96 | let area = (*self.patcher.get()).area(); 97 | region::protect_with_handle( 98 | area.as_ptr(), 99 | area.len(), 100 | region::Protection::READ_WRITE_EXECUTE, 101 | ) 102 | }?; 103 | 104 | // Copy either the detour or the original bytes of the function 105 | (*self.patcher.get()).toggle(enabled); 106 | self.enabled.store(enabled, Ordering::SeqCst); 107 | Ok(()) 108 | } 109 | } 110 | 111 | impl Drop for Detour { 112 | /// Disables the detour, if enabled. 113 | fn drop(&mut self) { 114 | let did_succeed = unsafe { self.disable() }.is_ok(); 115 | debug_assert!(did_succeed); 116 | } 117 | } 118 | 119 | impl fmt::Debug for Detour { 120 | /// Output whether the detour is enabled or not. 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | write!( 123 | f, 124 | "Detour {{ enabled: {}, trampoline: {:?} }}", 125 | self.is_enabled(), 126 | self.trampoline() 127 | ) 128 | } 129 | } 130 | 131 | unsafe impl Send for Detour {} 132 | unsafe impl Sync for Detour {} 133 | -------------------------------------------------------------------------------- /src/arch/memory.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | 3 | use crate::{alloc, arch, error::Result, pic}; 4 | use std::sync::Mutex; 5 | 6 | /// Shared allocator for all detours. 7 | pub static POOL: Lazy> = Lazy::new(|| { 8 | // Use a range of +/- 2 GB for seeking a memory block 9 | Mutex::new(alloc::ThreadAllocator::new(arch::meta::DETOUR_RANGE)) 10 | }); 11 | 12 | /// Allocates PIC code at the specified address. 13 | pub fn allocate_pic( 14 | pool: &mut alloc::ThreadAllocator, 15 | emitter: &pic::CodeEmitter, 16 | origin: *const (), 17 | ) -> Result { 18 | // Allocate memory close to the origin 19 | pool.allocate(origin, emitter.len()).map(|mut memory| { 20 | // Generate code for the obtained address 21 | let code = emitter.emit(memory.as_ptr() as *const _); 22 | memory.copy_from_slice(code.as_slice()); 23 | memory 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | /// Architecture specific code 2 | /// 3 | /// The current implementation requires a module to expose some functionality: 4 | /// 5 | /// - A standalone `relay_builder` function. 6 | /// This function creates a relay for targets with large displacement, that 7 | /// requires special attention. An example would be detours further away than 8 | /// 2GB on x64. A relative jump is not enough, so the `relay_builder` 9 | /// generates an absolute jump that the relative jump can reach. If it's 10 | /// needless, `None` can be returned. 11 | /// 12 | /// - A `Patcher`, modifies a target in-memory. 13 | /// - A `Trampoline`, generates a callable address to the target. 14 | pub use self::detour::Detour; 15 | 16 | use cfg_if::cfg_if; 17 | 18 | // TODO: flush instruction cache? __clear_cache 19 | // See: https://github.com/llvm-mirror/compiler-rt/blob/master/lib/builtins/clear_cache.c 20 | cfg_if! { 21 | if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { 22 | mod x86; 23 | use self::x86::{Patcher, Trampoline, meta}; 24 | } else { 25 | // TODO: Implement ARM/AARCH64/MIPS support! 26 | } 27 | } 28 | 29 | mod detour; 30 | mod memory; 31 | 32 | /// Returns true if the displacement is within a certain range. 33 | pub fn is_within_range(displacement: isize) -> bool { 34 | let range = meta::DETOUR_RANGE as i64; 35 | (-range..range).contains(&(displacement as i64)) 36 | } 37 | -------------------------------------------------------------------------------- /src/arch/x86/meta.rs: -------------------------------------------------------------------------------- 1 | use super::thunk; 2 | use crate::{error::Result, pic}; 3 | use std::mem; 4 | 5 | /// The furthest distance between a target and its detour (2 GiB). 6 | pub const DETOUR_RANGE: usize = 0x8000_0000; 7 | 8 | /// Returns the preferred prolog size for the target. 9 | pub fn prolog_margin(_target: *const ()) -> usize { 10 | mem::size_of::() 11 | } 12 | 13 | /// Creates a relay; required for destinations further away than 2GB (on x64). 14 | pub fn relay_builder(target: *const (), detour: *const ()) -> Result> { 15 | let displacement = (target as isize).wrapping_sub(detour as isize); 16 | 17 | if cfg!(target_arch = "x86_64") && !crate::arch::is_within_range(displacement) { 18 | let mut emitter = pic::CodeEmitter::new(); 19 | emitter.add_thunk(thunk::jmp(detour as usize)); 20 | Ok(Some(emitter)) 21 | } else { 22 | Ok(None) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/arch/x86/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::patcher::Patcher; 2 | pub use self::trampoline::Trampoline; 3 | 4 | pub mod meta; 5 | mod patcher; 6 | mod thunk; 7 | mod trampoline; 8 | 9 | // TODO: Add test for targets further away than DETOUR_RANGE 10 | // TODO: Add test for unsupported branches 11 | // TODO: Add test for negative branch displacements 12 | #[cfg(all(feature = "nightly", test))] 13 | mod tests { 14 | use crate::error::{Error, Result}; 15 | use crate::RawDetour; 16 | use matches::assert_matches; 17 | use std::arch::naked_asm; 18 | use std::mem; 19 | 20 | /// Default test case function definition. 21 | type CRet = unsafe extern "C" fn() -> i32; 22 | 23 | /// Detours a C function returning an integer, and asserts its return value. 24 | #[inline(never)] 25 | unsafe fn detour_test(target: CRet, result: i32) -> Result<()> { 26 | let hook = RawDetour::new(target as *const (), ret10 as *const ())?; 27 | 28 | assert_eq!(target(), result); 29 | hook.enable()?; 30 | { 31 | assert_eq!(target(), 10); 32 | let original: CRet = mem::transmute(hook.trampoline()); 33 | assert_eq!(original(), result); 34 | } 35 | hook.disable()?; 36 | assert_eq!(target(), result); 37 | Ok(()) 38 | } 39 | 40 | #[test] 41 | fn detour_relative_branch() -> Result<()> { 42 | #[unsafe(naked)] 43 | unsafe extern "C" fn branch_ret5() -> i32 { 44 | naked_asm!( 45 | " 46 | xor eax, eax 47 | je 5f 48 | mov eax, 2 49 | jmp 2f 50 | 5: 51 | mov eax, 5 52 | 2: 53 | ret", 54 | ); 55 | } 56 | 57 | unsafe { detour_test(mem::transmute(branch_ret5 as usize), 5) } 58 | } 59 | 60 | #[test] 61 | fn detour_hotpatch() -> Result<()> { 62 | #[unsafe(naked)] 63 | unsafe extern "C" fn hotpatch_ret0() -> i32 { 64 | naked_asm!( 65 | " 66 | nop 67 | nop 68 | nop 69 | nop 70 | nop 71 | xor eax, eax 72 | ret 73 | mov eax, 5", 74 | ); 75 | } 76 | 77 | unsafe { detour_test(mem::transmute(hotpatch_ret0 as usize + 5), 0) } 78 | } 79 | 80 | #[test] 81 | fn detour_padding_after() -> Result<()> { 82 | #[unsafe(naked)] 83 | unsafe extern "C" fn padding_after_ret0() -> i32 { 84 | naked_asm!( 85 | " 86 | mov edi, edi 87 | xor eax, eax 88 | ret 89 | nop 90 | nop", 91 | ); 92 | } 93 | 94 | unsafe { detour_test(mem::transmute(padding_after_ret0 as usize + 2), 0) } 95 | } 96 | 97 | #[test] 98 | fn detour_external_loop() { 99 | #[unsafe(naked)] 100 | unsafe extern "C" fn external_loop() -> i32 { 101 | naked_asm!( 102 | " 103 | loop 2f 104 | nop 105 | nop 106 | nop 107 | 2:", 108 | ); 109 | } 110 | 111 | let error = 112 | unsafe { RawDetour::new(external_loop as *const (), ret10 as *const ()) }.unwrap_err(); 113 | assert_matches!(error, Error::UnsupportedInstruction); 114 | } 115 | 116 | #[test] 117 | #[cfg(target_arch = "x86_64")] 118 | fn detour_rip_relative_pos() -> Result<()> { 119 | #[unsafe(naked)] 120 | unsafe extern "C" fn rip_relative_ret195() -> i32 { 121 | naked_asm!( 122 | " 123 | xor eax, eax 124 | mov al, [rip+0x3] 125 | nop 126 | nop 127 | nop 128 | ret", 129 | ); 130 | } 131 | 132 | unsafe { detour_test(rip_relative_ret195, 195) } 133 | } 134 | 135 | #[test] 136 | #[cfg(target_arch = "x86_64")] 137 | fn detour_rip_relative_neg() -> Result<()> { 138 | #[unsafe(naked)] 139 | unsafe extern "C" fn rip_relative_prolog_ret49() -> i32 { 140 | naked_asm!( 141 | " 142 | xor eax, eax 143 | mov al, [rip-0x8] 144 | ret", 145 | ); 146 | } 147 | 148 | unsafe { detour_test(rip_relative_prolog_ret49, 49) } 149 | } 150 | 151 | /// Default detour target. 152 | unsafe extern "C" fn ret10() -> i32 { 153 | 10 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/arch/x86/patcher.rs: -------------------------------------------------------------------------------- 1 | use super::thunk; 2 | use crate::error::{Error, Result}; 3 | use crate::{pic, util}; 4 | use std::{mem, slice}; 5 | 6 | pub struct Patcher { 7 | patch_area: &'static mut [u8], 8 | original_prolog: Vec, 9 | detour_prolog: Vec, 10 | } 11 | 12 | impl Patcher { 13 | /// Creates a new detour patcher for an address. 14 | /// 15 | /// # Arguments 16 | /// 17 | /// * `target` - An address that should be hooked. 18 | /// * `detour` - An address that the target should be redirected to. 19 | /// * `prolog_size` - The available inline space for the hook. 20 | pub unsafe fn new(target: *const (), detour: *const (), prolog_size: usize) -> Result { 21 | // Calculate the patch area (i.e if a short or long jump should be used) 22 | let patch_area = Self::patch_area(target, prolog_size)?; 23 | let emitter = Self::hook_template(detour, patch_area); 24 | 25 | let patch_address = patch_area.as_ptr() as *const (); 26 | let original_prolog = patch_area.to_vec(); 27 | 28 | Ok(Patcher { 29 | detour_prolog: emitter.emit(patch_address), 30 | original_prolog, 31 | patch_area, 32 | }) 33 | } 34 | 35 | /// Returns the target's patch area. 36 | pub fn area(&self) -> &[u8] { 37 | self.patch_area 38 | } 39 | 40 | /// Either patches or unpatches the function. 41 | pub unsafe fn toggle(&mut self, enable: bool) { 42 | // Copy either the detour or the original bytes of the function 43 | self.patch_area.copy_from_slice(if enable { 44 | &self.detour_prolog 45 | } else { 46 | &self.original_prolog 47 | }); 48 | } 49 | 50 | /// Returns the patch area for a function, consisting of a long jump and 51 | /// possibly a short jump. 52 | unsafe fn patch_area(target: *const (), prolog_size: usize) -> Result<&'static mut [u8]> { 53 | let jump_rel08_size = mem::size_of::(); 54 | let jump_rel32_size = mem::size_of::(); 55 | 56 | // Check if there isn't enough space for a relative long jump 57 | if !Self::is_patchable(target, prolog_size, jump_rel32_size) { 58 | // ... check if a relative small jump fits instead 59 | if Self::is_patchable(target, prolog_size, jump_rel08_size) { 60 | // A small jump relies on there being a hot patch area above the 61 | // function, that consists of at least 5 bytes (a rel32 jump). 62 | let hot_patch = target as usize - jump_rel32_size; 63 | let hot_patch_area = slice::from_raw_parts(hot_patch as *const u8, jump_rel32_size); 64 | 65 | // Ensure that the hot patch area only contains padding and is executable 66 | if !Self::is_code_padding(hot_patch_area) 67 | || !util::is_executable_address(hot_patch_area.as_ptr() as *const _)? 68 | { 69 | Err(Error::NoPatchArea)?; 70 | } 71 | 72 | // The range is from the start of the hot patch to the end of the jump 73 | let patch_size = jump_rel32_size + jump_rel08_size; 74 | Ok(slice::from_raw_parts_mut(hot_patch as *mut u8, patch_size)) 75 | } else { 76 | Err(Error::NoPatchArea) 77 | } 78 | } else { 79 | // The range is from the start of the function to the end of the jump 80 | Ok(slice::from_raw_parts_mut( 81 | target as *mut u8, 82 | jump_rel32_size, 83 | )) 84 | } 85 | } 86 | 87 | /// Creates a redirect code template for the targetted patch area. 88 | fn hook_template(detour: *const (), patch_area: &[u8]) -> pic::CodeEmitter { 89 | let mut emitter = pic::CodeEmitter::new(); 90 | 91 | // Both hot patch and normal detours use a relative long jump 92 | emitter.add_thunk(thunk::x86::jmp_rel32(detour as usize)); 93 | 94 | // The hot patch relies on a small jump to get to the long jump 95 | let jump_rel32_size = mem::size_of::(); 96 | let uses_hot_patch = patch_area.len() > jump_rel32_size; 97 | 98 | if uses_hot_patch { 99 | let displacement = -(jump_rel32_size as i8); 100 | emitter.add_thunk(thunk::x86::jmp_rel8(displacement)); 101 | } 102 | 103 | // Pad leftover bytes with nops 104 | while emitter.len() < patch_area.len() { 105 | emitter.add_thunk(thunk::x86::nop()); 106 | } 107 | 108 | emitter 109 | } 110 | 111 | /// Returns whether an address can be inline patched or not. 112 | unsafe fn is_patchable(target: *const (), prolog_size: usize, patch_size: usize) -> bool { 113 | if prolog_size >= patch_size { 114 | // If the whole patch fits it's good to go! 115 | return true; 116 | } 117 | 118 | // Otherwise the inline patch relies on padding after the prolog 119 | let slice = slice::from_raw_parts( 120 | (target as usize + prolog_size) as *const u8, 121 | patch_size - prolog_size, 122 | ); 123 | 124 | Self::is_code_padding(slice) 125 | } 126 | 127 | /// Returns true if the slice only contains code padding. 128 | fn is_code_padding(buffer: &[u8]) -> bool { 129 | const PADDING: [u8; 3] = [0x00, 0x90, 0xCC]; 130 | buffer.iter().all(|code| PADDING.contains(code)) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/arch/x86/thunk/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// Implements x86 operations 4 | pub mod x86; 5 | 6 | /// Implements x64 operations 7 | #[cfg(target_arch = "x86_64")] 8 | pub mod x64; 9 | 10 | #[cfg(target_arch = "x86")] 11 | mod arch { 12 | pub use super::x86::call_rel32 as call; 13 | pub use super::x86::jcc_rel32 as jcc; 14 | pub use super::x86::jmp_rel32 as jmp; 15 | } 16 | 17 | #[cfg(target_arch = "x86_64")] 18 | mod arch { 19 | pub use super::x64::call_abs as call; 20 | pub use super::x64::jcc_abs as jcc; 21 | pub use super::x64::jmp_abs as jmp; 22 | } 23 | 24 | // Export the default architecture 25 | pub use self::arch::*; 26 | -------------------------------------------------------------------------------- /src/arch/x86/thunk/x64.rs: -------------------------------------------------------------------------------- 1 | use crate::pic::Thunkable; 2 | use std::mem; 3 | 4 | #[repr(packed)] 5 | struct CallAbs { 6 | // call [rip+8] 7 | opcode0: u8, 8 | opcode1: u8, 9 | dummy0: u32, 10 | // jmp +10 11 | dummy1: u8, 12 | dummy2: u8, 13 | // destination 14 | address: usize, 15 | } 16 | 17 | pub fn call_abs(destination: usize) -> Box { 18 | let code = CallAbs { 19 | opcode0: 0xFF, 20 | opcode1: 0x15, 21 | dummy0: 0x0_0000_0002, 22 | dummy1: 0xEB, 23 | dummy2: 0x08, 24 | address: destination, 25 | }; 26 | 27 | let slice: [u8; 16] = unsafe { mem::transmute(code) }; 28 | Box::new(slice.to_vec()) 29 | } 30 | 31 | #[repr(packed)] 32 | struct JumpAbs { 33 | // jmp +6 34 | opcode0: u8, 35 | opcode1: u8, 36 | dummy0: u32, 37 | // destination 38 | address: usize, 39 | } 40 | 41 | pub fn jmp_abs(destination: usize) -> Box { 42 | let code = JumpAbs { 43 | opcode0: 0xFF, 44 | opcode1: 0x25, 45 | dummy0: 0x0_0000_0000, 46 | address: destination, 47 | }; 48 | 49 | let slice: [u8; 14] = unsafe { mem::transmute(code) }; 50 | Box::new(slice.to_vec()) 51 | } 52 | 53 | #[repr(packed)] 54 | struct JccAbs { 55 | // jxx + 16 56 | opcode: u8, 57 | dummy0: u8, 58 | dummy1: u8, 59 | dummy2: u8, 60 | dummy3: u32, 61 | // destination 62 | address: usize, 63 | } 64 | 65 | pub fn jcc_abs(destination: usize, condition: u8) -> Box { 66 | let code = JccAbs { 67 | // Invert the condition in x64 mode to simplify the conditional jump logic 68 | opcode: 0x71 ^ condition, 69 | dummy0: 0x0E, 70 | dummy1: 0xFF, 71 | dummy2: 0x25, 72 | dummy3: 0x0000_0000, 73 | address: destination, 74 | }; 75 | 76 | let slice: [u8; 16] = unsafe { mem::transmute(code) }; 77 | Box::new(slice.to_vec()) 78 | } 79 | -------------------------------------------------------------------------------- /src/arch/x86/thunk/x86.rs: -------------------------------------------------------------------------------- 1 | use crate::pic::{FixedThunk, Thunkable}; 2 | use generic_array::{typenum, GenericArray}; 3 | use std::mem; 4 | 5 | #[repr(packed)] 6 | pub struct JumpRel { 7 | opcode: u8, 8 | operand: u32, 9 | } 10 | 11 | /// Constructs either a relative jump or call. 12 | fn relative32(destination: usize, is_jump: bool) -> Box { 13 | const CALL: u8 = 0xE8; 14 | const JMP: u8 = 0xE9; 15 | 16 | Box::new(FixedThunk::::new(move |source| { 17 | let code = JumpRel { 18 | opcode: if is_jump { JMP } else { CALL }, 19 | operand: calculate_displacement(source, destination, mem::size_of::()), 20 | }; 21 | 22 | let slice: [u8; 5] = unsafe { mem::transmute(code) }; 23 | GenericArray::clone_from_slice(&slice) 24 | })) 25 | } 26 | 27 | /// Returns a no-op instruction. 28 | pub fn nop() -> Box { 29 | Box::new([0x90].to_vec()) 30 | } 31 | 32 | /// Constructs a relative call operation. 33 | pub fn call_rel32(destination: usize) -> Box { 34 | relative32(destination, false) 35 | } 36 | 37 | /// Constructs a relative jump operation. 38 | pub fn jmp_rel32(destination: usize) -> Box { 39 | relative32(destination, true) 40 | } 41 | 42 | #[repr(packed)] 43 | struct JccRel { 44 | opcode0: u8, 45 | opcode1: u8, 46 | operand: u32, 47 | } 48 | 49 | /// Constructs a conditional relative jump operation. 50 | pub fn jcc_rel32(destination: usize, condition: u8) -> Box { 51 | Box::new(FixedThunk::::new(move |source| { 52 | let code = JccRel { 53 | opcode0: 0x0F, 54 | opcode1: 0x80 | condition, 55 | operand: calculate_displacement(source, destination, mem::size_of::()), 56 | }; 57 | 58 | let slice: [u8; 6] = unsafe { mem::transmute(code) }; 59 | GenericArray::clone_from_slice(&slice) 60 | })) 61 | } 62 | 63 | #[repr(packed)] 64 | pub struct JumpShort { 65 | opcode: u8, 66 | operand: i8, 67 | } 68 | 69 | /// Constructs a relative short jump. 70 | pub fn jmp_rel8(displacement: i8) -> Box { 71 | Box::new(FixedThunk::::new(move |_| { 72 | let code = JumpShort { 73 | opcode: 0xEB, 74 | operand: displacement - mem::size_of::() as i8, 75 | }; 76 | 77 | let slice: [u8; 2] = unsafe { mem::transmute(code) }; 78 | GenericArray::clone_from_slice(&slice) 79 | })) 80 | } 81 | 82 | /// Calculates the relative displacement for an instruction. 83 | fn calculate_displacement(source: usize, destination: usize, instruction_size: usize) -> u32 { 84 | let displacement = 85 | (destination as isize).wrapping_sub(source as isize + instruction_size as isize); 86 | 87 | // Ensure that the detour can be reached with a relative jump (+/- 2GB). 88 | // This only needs to be asserted on x64, since it wraps around on x86. 89 | #[cfg(target_arch = "x86_64")] 90 | assert!(crate::arch::is_within_range(displacement)); 91 | 92 | displacement as u32 93 | } 94 | -------------------------------------------------------------------------------- /src/arch/x86/trampoline/disasm.rs: -------------------------------------------------------------------------------- 1 | //! The underlying disassembler should be opaque to the outside. 2 | use iced_x86::{Instruction, Mnemonic, OpKind, Register}; 3 | 4 | pub trait InstructionExt { 5 | /// Returns the instructions relative branch offset, if applicable. 6 | fn relative_branch_target(&self) -> Option; 7 | /// Returns the instructions RIP operand displacement if applicable. 8 | fn rip_operand_target(&self) -> Option; 9 | /// Returns true if this instruction any type of a loop. 10 | fn is_loop(&self) -> bool; 11 | /// Returns true if this instruction is an unconditional jump. 12 | fn is_unconditional_jump(&self) -> bool; 13 | /// Returns true if this instruction is a function call. 14 | fn is_call(&self) -> bool; 15 | /// Returns true if this instruction is a return. 16 | fn is_return(&self) -> bool; 17 | } 18 | 19 | impl InstructionExt for Instruction { 20 | /// Returns the instructions relative branch offset, if applicable. 21 | fn relative_branch_target(&self) -> Option { 22 | use OpKind::*; 23 | match self.op0_kind() { 24 | NearBranch16 | NearBranch32 | NearBranch64 => Some(self.near_branch_target()), 25 | _ => None, 26 | } 27 | } 28 | 29 | /// Returns the instructions RIP operand displacement if applicable. 30 | fn rip_operand_target(&self) -> Option { 31 | self 32 | .op_kinds() 33 | .find(|op| *op == OpKind::Memory && self.memory_base() == Register::RIP) 34 | .map(|_| self.memory_displacement64()) 35 | } 36 | 37 | /// Returns true if this instruction any type of a loop. 38 | fn is_loop(&self) -> bool { 39 | use Mnemonic::*; 40 | matches!(self.mnemonic(), Loop | Loope | Loopne | Jecxz | Jcxz) 41 | } 42 | 43 | /// Returns true if this instruction is an unconditional jump. 44 | fn is_unconditional_jump(&self) -> bool { 45 | self.mnemonic() == Mnemonic::Jmp 46 | } 47 | 48 | /// Returns true if this instruction is a function call. 49 | fn is_call(&self) -> bool { 50 | self.mnemonic() == Mnemonic::Call 51 | } 52 | 53 | /// Returns true if this instruction is a return. 54 | fn is_return(&self) -> bool { 55 | self.mnemonic() == Mnemonic::Ret 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/arch/x86/trampoline/mod.rs: -------------------------------------------------------------------------------- 1 | use self::disasm::*; 2 | use crate::arch::x86::thunk; 3 | use crate::error::{Error, Result}; 4 | use crate::pic; 5 | use iced_x86::{Decoder, DecoderOptions, Instruction, OpKind}; 6 | use std::ptr::slice_from_raw_parts; 7 | use std::{mem, slice}; 8 | 9 | mod disasm; 10 | 11 | /// A trampoline generator (x86/x64). 12 | pub struct Trampoline { 13 | emitter: pic::CodeEmitter, 14 | prolog_size: usize, 15 | } 16 | 17 | impl Trampoline { 18 | /// Constructs a new trampoline for an address. 19 | pub unsafe fn new(target: *const (), margin: usize) -> Result { 20 | Builder::new(target, margin).build() 21 | } 22 | 23 | /// Returns a reference to the trampoline's code emitter. 24 | pub fn emitter(&self) -> &pic::CodeEmitter { 25 | &self.emitter 26 | } 27 | 28 | /// Returns the size of the prolog (i.e the amount of disassembled bytes). 29 | pub fn prolog_size(&self) -> usize { 30 | self.prolog_size 31 | } 32 | } 33 | 34 | /// A trampoline builder. 35 | struct Builder { 36 | /// Target destination for a potential internal branch. 37 | branch_address: Option, 38 | /// Total amount of bytes disassembled. 39 | total_bytes_disassembled: usize, 40 | /// The preferred minimum amount of bytes disassembled. 41 | margin: usize, 42 | /// Whether disassembling has finished or not. 43 | finished: bool, 44 | /// The target the trampoline is adapted for. 45 | target: *const (), 46 | } 47 | 48 | impl Builder { 49 | /// Returns a trampoline builder. 50 | pub fn new(target: *const (), margin: usize) -> Self { 51 | Builder { 52 | branch_address: None, 53 | total_bytes_disassembled: 0, 54 | finished: false, 55 | target, 56 | margin, 57 | } 58 | } 59 | 60 | /// Creates a trampoline with the supplied settings. 61 | /// 62 | /// # Safety 63 | /// 64 | /// target..target+margin+15 must be valid to read as a u8 slice or behavior 65 | /// may be undefined 66 | pub unsafe fn build(mut self) -> Result { 67 | let mut emitter = pic::CodeEmitter::new(); 68 | 69 | // 15 = max size of x64 instruction 70 | // safety: we don't know the end address of a function so this could be too far 71 | // if the function is right at the end of the code section iced_x86 decoder 72 | // doesn't have a way to read one byte at a time without creating a slice in 73 | // advance and it's invalid to make a slice that's too long we could make a 74 | // new Decoder before reading every individual instruction? but it'd still need 75 | // to be given a 15 byte slice to handle any valid x64 instruction 76 | let target: *const u8 = self.target.cast(); 77 | let slice = unsafe { slice::from_raw_parts(std::hint::black_box(target), self.margin + 15) }; 78 | let decoder = Decoder::with_ip( 79 | (mem::size_of::() * 8) as u32, 80 | slice, 81 | self.target as u64, 82 | DecoderOptions::NONE, 83 | ); 84 | for instruction in decoder { 85 | if instruction.is_invalid() { 86 | break; 87 | } 88 | self.total_bytes_disassembled += instruction.len(); 89 | let instr_offset = instruction.ip() as usize - (self.target as usize); 90 | let instruction_bytes = &slice[instr_offset..instr_offset + instruction.len()]; 91 | let thunk = self.process_instruction(&instruction, instruction_bytes)?; 92 | 93 | // If the trampoline displacement is larger than the target 94 | // function, all instructions will be displaced, and if there is 95 | // internal branching, it will end up at the wrong instructions. 96 | if self.is_instruction_in_branch(&instruction) && instruction.len() != thunk.len() { 97 | Err(Error::UnsupportedInstruction)?; 98 | } else { 99 | emitter.add_thunk(thunk); 100 | } 101 | 102 | // Determine whether enough bytes for the margin has been disassembled 103 | if self.total_bytes_disassembled >= self.margin && !self.finished { 104 | // Add a jump to the first instruction after the prolog 105 | emitter.add_thunk(thunk::jmp(instruction.next_ip() as usize)); 106 | self.finished = true; 107 | } 108 | 109 | if self.finished { 110 | break; 111 | } 112 | } 113 | 114 | Ok(Trampoline { 115 | prolog_size: self.total_bytes_disassembled, 116 | emitter, 117 | }) 118 | } 119 | 120 | /// Returns an instruction after analysing and potentially modifies it. 121 | unsafe fn process_instruction( 122 | &mut self, 123 | instruction: &Instruction, 124 | instruction_bytes: &[u8], 125 | ) -> Result> { 126 | if let Some(target) = instruction.rip_operand_target() { 127 | return self.handle_rip_relative_instruction(instruction, instruction_bytes, target as usize); 128 | } else if let Some(target) = instruction.relative_branch_target() { 129 | return self.handle_relative_branch(instruction, instruction_bytes, target as usize); 130 | } else if instruction.is_return() { 131 | // In case the operand is not placed in a branch, the function 132 | // returns unconditionally (i.e it terminates here). 133 | self.finished = !self.is_instruction_in_branch(instruction); 134 | } 135 | 136 | // The instruction does not use any position-dependant operands, 137 | // therefore the bytes can be copied directly from source. 138 | Ok(Box::new(instruction_bytes.to_vec())) 139 | } 140 | 141 | /// Adjusts the offsets for RIP relative operands. They are only available 142 | /// in x64 processes. The operands offsets needs to be adjusted for their 143 | /// new position. An example would be: 144 | /// 145 | /// ```asm 146 | /// mov eax, [rip+0x10] ; the displacement before relocation 147 | /// mov eax, [rip+0x4892] ; theoretical adjustment after relocation 148 | /// ``` 149 | unsafe fn handle_rip_relative_instruction( 150 | &mut self, 151 | instruction: &Instruction, 152 | instruction_bytes: &[u8], 153 | target: usize, 154 | ) -> Result> { 155 | let displacement = target 156 | .wrapping_sub(instruction.ip() as usize) 157 | .wrapping_sub(instruction.len()) as isize; 158 | // If the instruction is an unconditional jump, processing stops here 159 | self.finished = instruction.is_unconditional_jump(); 160 | 161 | // Nothing should be done if `displacement` is within the prolog. 162 | if (-(self.total_bytes_disassembled as isize)..0).contains(&displacement) { 163 | return Ok(Box::new(instruction_bytes.to_vec())); 164 | } 165 | 166 | // These need to be captured by the closure 167 | let instruction_address = instruction.ip() as isize; 168 | let instruction_bytes = instruction_bytes.to_vec(); 169 | let immediate_size = instruction.op_kinds().find_map(|kind| { 170 | match kind { 171 | OpKind::Immediate8 => Some(1), 172 | OpKind::Immediate8_2nd => Some(1), 173 | OpKind::Immediate16 => Some(2), 174 | OpKind::Immediate32 => Some(4), 175 | OpKind::Immediate64 => Some(8), 176 | OpKind::Immediate8to16 => Some(1), 177 | OpKind::Immediate8to32 => Some(1), 178 | OpKind::Immediate8to64 => Some(1), 179 | OpKind::Immediate32to64 => Some(4), 180 | _ => None, 181 | } 182 | }).unwrap_or(0); 183 | 184 | Ok(Box::new(pic::UnsafeThunk::new( 185 | move |offset| { 186 | let mut bytes = instruction_bytes.clone(); 187 | 188 | // Calculate the new relative displacement for the operand. The 189 | // instruction is relative so the offset (i.e where the trampoline is 190 | // allocated), must be within a range of +/- 2GB. 191 | let adjusted_displacement = instruction_address 192 | .wrapping_sub(offset as isize) 193 | .wrapping_add(displacement); 194 | assert!(crate::arch::is_within_range(adjusted_displacement)); 195 | 196 | // The displacement value is placed at (instruction - disp32 - imm) 197 | let index = instruction_bytes.len() - mem::size_of::() - immediate_size; 198 | 199 | // Write the adjusted displacement offset to the operand 200 | let as_bytes: [u8; 4] = (adjusted_displacement as u32).to_ne_bytes(); 201 | bytes[index..index+as_bytes.len()].copy_from_slice(&as_bytes); 202 | bytes 203 | }, 204 | instruction.len(), 205 | ))) 206 | } 207 | 208 | /// Processes relative branches (e.g `call`, `loop`, `jne`). 209 | unsafe fn handle_relative_branch( 210 | &mut self, 211 | instruction: &Instruction, 212 | instruction_bytes: &[u8], 213 | destination_address_abs: usize, 214 | ) -> Result> { 215 | if instruction.is_call() { 216 | // Calls are not an issue since they return to the original address 217 | return Ok(thunk::call(destination_address_abs)); 218 | } 219 | 220 | let prolog_range = (self.target as usize)..(self.target as usize + self.margin); 221 | 222 | // If the relative jump is internal, and short enough to 223 | // fit within the copied function prolog (i.e `margin`), 224 | // the jump instruction can be copied indiscriminately. 225 | if prolog_range.contains(&destination_address_abs) { 226 | // Keep track of the jump's destination address 227 | self.branch_address = Some(destination_address_abs); 228 | Ok(Box::new(instruction_bytes.to_vec())) 229 | } else if instruction.is_loop() { 230 | // Loops (e.g 'loopnz', 'jecxz') to the outside are not supported 231 | Err(Error::UnsupportedInstruction) 232 | } else if instruction.is_unconditional_jump() { 233 | // If the function is not in a branch, and it unconditionally jumps 234 | // a distance larger than the prolog, it's the same as if it terminates. 235 | self.finished = !self.is_instruction_in_branch(instruction); 236 | Ok(thunk::jmp(destination_address_abs)) 237 | } else { 238 | // Conditional jumps (Jcc) 239 | // To extract the condition, the primary opcode is required. Short 240 | // jumps are only one byte, but long jccs are prefixed with 0x0F. 241 | let primary_opcode = instruction_bytes 242 | .iter() 243 | .find(|op| **op != 0x0F) 244 | .expect("retrieving conditional jump primary op code"); 245 | 246 | // Extract the condition (i.e 0x74 is [jz rel8] ⟶ 0x74 & 0x0F == 4) 247 | let condition = primary_opcode & 0x0F; 248 | Ok(thunk::jcc(destination_address_abs, condition)) 249 | } 250 | } 251 | 252 | /// Returns whether the current instruction is inside a branch or not. 253 | fn is_instruction_in_branch(&self, instruction: &Instruction) -> bool { 254 | self 255 | .branch_address 256 | .map_or(false, |offset| instruction.ip() < offset as u64) 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/detours/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::Detour; 2 | use crate::error::Result; 3 | use crate::{Function, HookableWith}; 4 | use std::marker::PhantomData; 5 | 6 | /// A type-safe detour. 7 | /// 8 | /// Due to being generated by a macro, the `GenericDetour::call` method is not 9 | /// exposed in the documentation. 10 | /// It accepts the same arguments as `T`, and shares its result type: 11 | /// 12 | /// ```c 13 | /// /// Calls the original function regardless of whether it's hooked or not. 14 | /// fn call(&self, T::Arguments) -> T::Output 15 | /// ``` 16 | /// 17 | /// # Example 18 | /// 19 | /// ```rust 20 | /// # use retour::Result; 21 | /// use retour::GenericDetour; 22 | /// 23 | /// fn add5(val: i32) -> i32 { 24 | /// val + 5 25 | /// } 26 | /// 27 | /// fn add10(val: i32) -> i32 { 28 | /// val + 10 29 | /// } 30 | /// 31 | /// # fn main() -> Result<()> { 32 | /// let mut hook = unsafe { GenericDetour:: i32>::new(add5, add10)? }; 33 | /// 34 | /// assert_eq!(add5(5), 10); 35 | /// assert_eq!(hook.call(5), 10); 36 | /// 37 | /// unsafe { hook.enable()? }; 38 | /// 39 | /// assert_eq!(add5(5), 15); 40 | /// assert_eq!(hook.call(5), 10); 41 | /// 42 | /// unsafe { hook.disable()? }; 43 | /// 44 | /// assert_eq!(add5(5), 10); 45 | /// # Ok(()) 46 | /// # } 47 | /// ``` 48 | #[derive(Debug)] 49 | pub struct GenericDetour { 50 | phantom: PhantomData, 51 | detour: Detour, 52 | } 53 | 54 | impl GenericDetour { 55 | /// Create a new hook given a target function and a compatible detour 56 | /// function. 57 | pub unsafe fn new(target: T, detour: D) -> Result 58 | where 59 | T: HookableWith, 60 | D: Function, 61 | { 62 | Detour::new(target.to_ptr(), detour.to_ptr()).map(|detour| GenericDetour { 63 | phantom: PhantomData, 64 | detour, 65 | }) 66 | } 67 | 68 | /// Enables the detour. 69 | pub unsafe fn enable(&self) -> Result<()> { 70 | self.detour.enable() 71 | } 72 | 73 | /// Disables the detour. 74 | pub unsafe fn disable(&self) -> Result<()> { 75 | self.detour.disable() 76 | } 77 | 78 | /// Returns whether the detour is enabled or not. 79 | pub fn is_enabled(&self) -> bool { 80 | self.detour.is_enabled() 81 | } 82 | 83 | /// Returns a reference to the generated trampoline. 84 | pub fn trampoline(&self) -> &() { 85 | self.detour.trampoline() 86 | } 87 | } 88 | 89 | unsafe impl Send for GenericDetour {} 90 | unsafe impl Sync for GenericDetour {} 91 | -------------------------------------------------------------------------------- /src/detours/mod.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | mod generic; 4 | mod raw; 5 | 6 | pub use self::generic::*; 7 | pub use self::raw::*; 8 | 9 | cfg_if! { 10 | if #[cfg(feature = "static-detour")] { 11 | #[cfg_attr(docsrs, doc(cfg(feature = "static-detour")))] 12 | mod statik; 13 | pub use self::statik::*; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/detours/raw.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::Detour; 2 | use crate::error::Result; 3 | 4 | /// A raw detour. 5 | /// 6 | /// # Example 7 | /// 8 | /// ```rust 9 | /// # use retour::Result; 10 | /// use retour::RawDetour; 11 | /// use std::mem; 12 | /// 13 | /// fn add5(val: i32) -> i32 { 14 | /// val + 5 15 | /// } 16 | /// 17 | /// fn add10(val: i32) -> i32 { 18 | /// val + 10 19 | /// } 20 | /// 21 | /// # fn main() -> Result<()> { 22 | /// let mut hook = unsafe { RawDetour::new(add5 as *const (), add10 as *const ())? }; 23 | /// 24 | /// assert_eq!(add5(5), 10); 25 | /// assert_eq!(hook.is_enabled(), false); 26 | /// 27 | /// unsafe { hook.enable()? }; 28 | /// assert!(hook.is_enabled()); 29 | /// 30 | /// let original: fn(i32) -> i32 = unsafe { mem::transmute(hook.trampoline()) }; 31 | /// 32 | /// assert_eq!(add5(5), 15); 33 | /// assert_eq!(original(5), 10); 34 | /// 35 | /// unsafe { hook.disable()? }; 36 | /// assert_eq!(add5(5), 10); 37 | /// # Ok(()) 38 | /// # } 39 | /// ``` 40 | #[derive(Debug)] 41 | pub struct RawDetour(Detour); 42 | 43 | // TODO: stop all threads in target during patch? 44 | impl RawDetour { 45 | /// Constructs a new inline detour patcher. 46 | /// 47 | /// The hook is disabled by default. Even when this function is succesful, 48 | /// there is no guaranteee that the detour function will actually get called 49 | /// when the target function gets called. An invocation of the target 50 | /// function might for example get inlined in which case it is impossible to 51 | /// hook at runtime. 52 | pub unsafe fn new(target: *const (), detour: *const ()) -> Result { 53 | Detour::new(target, detour).map(RawDetour) 54 | } 55 | 56 | /// Enables the detour. 57 | pub unsafe fn enable(&self) -> Result<()> { 58 | self.0.enable() 59 | } 60 | 61 | /// Disables the detour. 62 | pub unsafe fn disable(&self) -> Result<()> { 63 | self.0.disable() 64 | } 65 | 66 | /// Returns whether the detour is enabled or not. 67 | pub fn is_enabled(&self) -> bool { 68 | self.0.is_enabled() 69 | } 70 | 71 | /// Returns a reference to the generated trampoline. 72 | pub fn trampoline(&self) -> &() { 73 | self.0.trampoline() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/detours/statik.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::{Function, GenericDetour}; 3 | use std::marker::Tuple; 4 | use std::sync::atomic::{AtomicPtr, Ordering}; 5 | use std::{mem, ptr}; 6 | 7 | /// A type-safe static detour. 8 | /// 9 | /// Due to being generated by a macro, the `StaticDetour::call` method is not 10 | /// exposed in the documentation. 11 | /// 12 | /// ```c 13 | /// /// Calls the original function regardless of whether it's hooked or not. 14 | /// /// 15 | /// /// Panics if called when the static detour has not yet been initialized. 16 | /// fn call(&self, T::Arguments) -> T::Output 17 | /// ``` 18 | /// 19 | /// To define a static detour, use the 20 | /// [static_detour](./macro.static_detour.html) macro. 21 | /// 22 | /// # Example 23 | /// 24 | /// ```rust 25 | /// use std::error::Error; 26 | /// use retour::static_detour; 27 | /// 28 | /// static_detour! { 29 | /// static Test: fn(i32) -> i32; 30 | /// } 31 | /// 32 | /// fn add5(val: i32) -> i32 { 33 | /// val + 5 34 | /// } 35 | /// 36 | /// fn add10(val: i32) -> i32 { 37 | /// val + 10 38 | /// } 39 | /// 40 | /// fn main() -> Result<(), Box> { 41 | /// // Replace the 'add5' function with 'add10' (can also be a closure) 42 | /// unsafe { Test.initialize(add5, add10)? }; 43 | /// 44 | /// assert_eq!(add5(1), 6); 45 | /// assert_eq!(Test.call(1), 6); 46 | /// 47 | /// unsafe { Test.enable()? }; 48 | /// 49 | /// // The original function is detoured to 'add10' 50 | /// assert_eq!(add5(1), 11); 51 | /// 52 | /// // The original function can still be invoked using 'call' 53 | /// assert_eq!(Test.call(1), 6); 54 | /// 55 | /// // It is also possible to change the detour whilst hooked 56 | /// Test.set_detour(|val| val - 5); 57 | /// assert_eq!(add5(5), 0); 58 | /// 59 | /// unsafe { Test.disable()? }; 60 | /// 61 | /// assert_eq!(add5(1), 6); 62 | /// Ok(()) 63 | /// } 64 | /// ``` 65 | pub struct StaticDetour { 66 | closure: AtomicPtr>>, 67 | detour: AtomicPtr>, 68 | ffi: T, 69 | } 70 | 71 | impl StaticDetour { 72 | /// Create a new static detour. 73 | #[doc(hidden)] 74 | pub const fn __new(ffi: T) -> Self { 75 | StaticDetour { 76 | closure: AtomicPtr::new(ptr::null_mut()), 77 | detour: AtomicPtr::new(ptr::null_mut()), 78 | ffi, 79 | } 80 | } 81 | 82 | /// Create a new hook given a target function and a compatible detour 83 | /// closure. 84 | /// 85 | /// This method can only be called once per static instance. Multiple calls 86 | /// will error with `AlreadyExisting`. 87 | /// 88 | /// It returns `&self` to allow chaining initialization and activation: 89 | /// 90 | /// ```rust 91 | /// # use retour::{Result, static_detour}; 92 | /// # static_detour! { 93 | /// # static Test: fn(i32) -> i32; 94 | /// # } 95 | /// # 96 | /// # fn add5(val: i32) -> i32 { 97 | /// # val + 5 98 | /// # } 99 | /// # 100 | /// # fn main() -> Result<()> { 101 | /// unsafe { Test.initialize(add5, |x| x - 5)?.enable()? }; 102 | /// # Ok(()) 103 | /// # } 104 | /// ``` 105 | pub unsafe fn initialize(&self, target: T, closure: D) -> Result<&Self> 106 | where 107 | D: Fn + Send + 'static, 108 | ::Arguments: Tuple, 109 | { 110 | let mut detour = Box::new(GenericDetour::new(target, self.ffi)?); 111 | if self 112 | .detour 113 | .compare_exchange( 114 | ptr::null_mut(), 115 | &mut *detour, 116 | Ordering::SeqCst, 117 | Ordering::SeqCst, 118 | ) 119 | .is_err() 120 | { 121 | Err(Error::AlreadyInitialized)?; 122 | } 123 | 124 | self.set_detour(closure); 125 | mem::forget(detour); 126 | Ok(self) 127 | } 128 | 129 | /// Enables the detour. 130 | pub unsafe fn enable(&self) -> Result<()> { 131 | self 132 | .detour 133 | .load(Ordering::SeqCst) 134 | .as_ref() 135 | .ok_or(Error::NotInitialized)? 136 | .enable() 137 | } 138 | 139 | /// Disables the detour. 140 | pub unsafe fn disable(&self) -> Result<()> { 141 | self 142 | .detour 143 | .load(Ordering::SeqCst) 144 | .as_ref() 145 | .ok_or(Error::NotInitialized)? 146 | .disable() 147 | } 148 | 149 | /// Returns whether the detour is enabled or not. 150 | pub fn is_enabled(&self) -> bool { 151 | unsafe { self.detour.load(Ordering::SeqCst).as_ref() } 152 | .map(|detour| detour.is_enabled()) 153 | .unwrap_or(false) 154 | } 155 | 156 | /// Changes the detour, regardless of whether the hook is enabled or not. 157 | pub fn set_detour(&self, closure: C) 158 | where 159 | C: Fn + Send + 'static, 160 | ::Arguments: Tuple, 161 | { 162 | let previous = self 163 | .closure 164 | .swap(Box::into_raw(Box::new(Box::new(closure))), Ordering::SeqCst); 165 | if !previous.is_null() { 166 | mem::drop(unsafe { Box::from_raw(previous) }); 167 | } 168 | } 169 | 170 | /// Returns a reference to the generated trampoline. 171 | pub fn trampoline(&self) -> Result<&()> { 172 | Ok( 173 | unsafe { self.detour.load(Ordering::SeqCst).as_ref() } 174 | .ok_or(Error::NotInitialized)? 175 | .trampoline(), 176 | ) 177 | } 178 | 179 | /// Returns a transient reference to the active detour. 180 | #[doc(hidden)] 181 | pub fn __detour(&self) -> &dyn Fn 182 | where 183 | ::Arguments: Tuple, 184 | { 185 | // TODO: This is not 100% thread-safe in case the thread is stopped 186 | unsafe { self.closure.load(Ordering::SeqCst).as_ref() } 187 | .ok_or(Error::NotInitialized) 188 | .expect("retrieving detour closure") 189 | } 190 | } 191 | 192 | impl Drop for StaticDetour { 193 | fn drop(&mut self) { 194 | let previous = self.closure.swap(ptr::null_mut(), Ordering::Relaxed); 195 | if !previous.is_null() { 196 | mem::drop(unsafe { Box::from_raw(previous) }); 197 | } 198 | 199 | let previous = self.detour.swap(ptr::null_mut(), Ordering::Relaxed); 200 | if !previous.is_null() { 201 | unsafe { let _ = Box::from_raw(previous); }; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types and utilities. 2 | 3 | use std::error::Error as StdError; 4 | use std::fmt; 5 | 6 | /// The result of a detour operation. 7 | pub type Result = ::std::result::Result; 8 | 9 | /// A representation of all possible errors. 10 | #[derive(Debug)] 11 | pub enum Error { 12 | /// The address for the target and detour are identical 13 | SameAddress, 14 | /// The address does not contain valid instructions. 15 | InvalidCode, 16 | /// The address has no available area for patching. 17 | NoPatchArea, 18 | /// The address is not executable memory. 19 | NotExecutable, 20 | /// The detour is not initialized. 21 | NotInitialized, 22 | /// The detour is already initialized. 23 | AlreadyInitialized, 24 | /// The system is out of executable memory. 25 | OutOfMemory, 26 | /// The address contains an instruction that prevents detouring. 27 | UnsupportedInstruction, 28 | /// A memory operation failed. 29 | RegionFailure(region::Error), 30 | } 31 | 32 | impl StdError for Error { 33 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 34 | if let Error::RegionFailure(error) = self { 35 | Some(error) 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | 42 | impl fmt::Display for Error { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | match self { 45 | Error::SameAddress => write!(f, "Target and detour address is the same"), 46 | Error::InvalidCode => write!(f, "Address contains invalid assembly"), 47 | Error::NoPatchArea => write!(f, "Cannot find an inline patch area"), 48 | Error::NotExecutable => write!(f, "Address is not executable"), 49 | Error::NotInitialized => write!(f, "Detour is not initialized"), 50 | Error::AlreadyInitialized => write!(f, "Detour is already initialized"), 51 | Error::OutOfMemory => write!(f, "Cannot allocate memory"), 52 | Error::UnsupportedInstruction => write!(f, "Address contains an unsupported instruction"), 53 | Error::RegionFailure(ref error) => write!(f, "{}", error), 54 | } 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(error: region::Error) -> Self { 60 | Error::RegionFailure(error) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "1024"] 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | #![cfg_attr( 4 | feature = "static-detour", 5 | feature(unboxed_closures, tuple_trait) 6 | )] 7 | #![cfg_attr( 8 | all(feature = "static-detour", test), 9 | feature(naked_functions) 10 | )] 11 | 12 | //! A cross-platform detour library written in Rust. 13 | //! 14 | //! ## Intro 15 | //! 16 | //! This library provides a thread-safe, inline detouring functionality by 17 | //! disassembling and patching functions during runtime, using assembly opcodes 18 | //! allocated within executable memory. It modifies the target functions and 19 | //! replaces their prolog with an unconditional jump. 20 | //! 21 | //! Beyond the basic functionality this library handles several different edge 22 | //! cases: 23 | //! 24 | //! - Relative branches. 25 | //! - RIP relative operands. 26 | //! - Detects NOP-padding. 27 | //! - Relay for large offsets (>2GB). 28 | //! - Supports hot patching. 29 | //! 30 | //! ## Detours 31 | //! 32 | //! Three different types of detours are provided: 33 | //! 34 | //! - [Static](./struct.StaticDetour.html): A static & type-safe interface. 35 | //! Thanks to its static nature it can accept a closure as its detour, but is 36 | //! required to be statically defined at compile time. 37 | //! 38 | //! - [Generic](./struct.GenericDetour.html): A type-safe interface — the same 39 | //! prototype is enforced for both the target and the detour. It is also 40 | //! enforced when invoking the original target. 41 | //! 42 | //! - [Raw](./struct.RawDetour.html): The underlying building block that the 43 | //! others types abstract upon. It has no type-safety and interacts with raw 44 | //! pointers. It should be avoided unless any types are references, or not 45 | //! known until runtime. 46 | //! 47 | //! ## Supported Versions 48 | //! This crate, with default features, will support the MSRV in `Cargo.toml` 49 | //! (currently 1.60.0). Certain features may require newer versions of the 50 | //! compiler, which will be documented here and in the docs. Any features 51 | //! that require the nightly compiler will always target the newest version. 52 | //! 53 | //! ## Features 54 | //! 55 | //! - **static-detour**: Required for static detours, due to usage 56 | //! of *unboxed_closures* and *tuple_trait*. The feature also enables a more 57 | //! extensive test suite. *Requires nightly compiler* 58 | //! - **thiscall-abi**: Required for hooking functions that use the "thiscall" ABI. *Requires 1.73.0 or greater* 59 | //! - **28-args**: Allows for detouring functions up to 28 arguments (default is 14) 60 | //! - **42-args**: Allows for detouring functions up to 42 arguments 61 | //! 62 | //! ## Platforms 63 | //! 64 | //! - Both `x86` & `x86-64` are supported. 65 | //! 66 | //! ## Procedure 67 | //! 68 | //! To illustrate a detour on an x86 platform: 69 | //! 70 | //! ```c 71 | //! 0 int return_five() { 72 | //! 1 return 5; 73 | //! 00400020 [b8 05 00 00 00] mov eax, 5 74 | //! 00400025 [c3] ret 75 | //! 2 } 76 | //! 3 77 | //! 4 int detour_function() { 78 | //! 5 return 10; 79 | //! 00400040 [b8 0A 00 00 00] mov eax, 10 80 | //! 00400045 [c3] ret 81 | //! 6 } 82 | //! ``` 83 | //! 84 | //! To detour `return_five` the library by default tries to replace five bytes 85 | //! with a relative jump (the optimal scenario), which works in this case. 86 | //! Executable memory will be allocated for the instruction and the function's 87 | //! prolog will be replaced. 88 | //! 89 | //! ```c 90 | //! 0 int return_five() { 91 | //! 1 return detour_function(); 92 | //! 00400020 [e9 16 00 00 00] jmp 1b 93 | //! 00400025 [c3] ret 94 | //! 2 } 95 | //! 3 96 | //! 4 int detour_function() { 97 | //! 5 return 10; 98 | //! 00400040 [b8 0a 00 00 00] mov eax, 10 99 | //! 00400045 [c3] ret 100 | //! 6 } 101 | //! ``` 102 | //! 103 | //! Beyond what is shown here, a trampoline is also generated so the original 104 | //! function can be called regardless whether the function is hooked or not. 105 | //! 106 | //! For various injection methods, see the [README in the GitHub repo](https://github.com/Hpmason/retour-rs) 107 | 108 | // Re-exports 109 | pub use detours::*; 110 | pub use error::{Error, Result}; 111 | pub use traits::{Function, HookableWith}; 112 | 113 | #[macro_use] 114 | mod macros; 115 | 116 | // Modules 117 | mod alloc; 118 | mod arch; 119 | mod detours; 120 | mod error; 121 | mod pic; 122 | mod traits; 123 | mod util; 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | use crate::Result; 129 | use matches::assert_matches; 130 | 131 | #[test] 132 | fn detours_share_target() -> Result<()> { 133 | #[inline(never)] 134 | extern "C" fn add(x: i32, y: i32) -> i32 { 135 | unsafe { std::ptr::read_volatile(&x as *const i32) + y } 136 | } 137 | 138 | let hook1 = unsafe { 139 | extern "C" fn sub(x: i32, y: i32) -> i32 { 140 | x - y 141 | } 142 | GenericDetour:: i32>::new(add, sub)? 143 | }; 144 | 145 | unsafe { hook1.enable()? }; 146 | assert_eq!(add(5, 5), 0); 147 | 148 | let hook2 = unsafe { 149 | extern "C" fn div(x: i32, y: i32) -> i32 { 150 | x / y 151 | } 152 | GenericDetour:: i32>::new(add, div)? 153 | }; 154 | 155 | unsafe { hook2.enable()? }; 156 | 157 | // This will call the previous hook's detour 158 | assert_eq!(hook2.call(5, 5), 0); 159 | assert_eq!(add(10, 5), 2); 160 | Ok(()) 161 | } 162 | 163 | #[test] 164 | fn same_detour_and_target() { 165 | #[inline(never)] 166 | extern "C" fn add(x: i32, y: i32) -> i32 { 167 | unsafe { std::ptr::read_volatile(&x as *const i32) + y } 168 | } 169 | 170 | let err = unsafe { RawDetour::new(add as *const (), add as *const ()).unwrap_err() }; 171 | assert_matches!(err, Error::SameAddress); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// A macro for defining static, type-safe detours. 2 | /// 3 | /// This macro defines one or more [StaticDetour](./struct.StaticDetour.html)s. 4 | /// 5 | /// 6 | /// # Syntax 7 | /// 8 | /// ```ignore 9 | /// static_detour! { 10 | /// [pub] static NAME_1: [unsafe] [extern "cc"] fn([argument]...) [-> ret]; 11 | /// [pub] static NAME_2: [unsafe] [extern "cc"] fn([argument]...) [-> ret]; 12 | /// ... 13 | /// [pub] static NAME_N: [unsafe] [extern "cc"] fn([argument]...) [-> ret]; 14 | /// } 15 | /// ``` 16 | /// 17 | /// # Example 18 | /// 19 | /// ```rust 20 | /// # use retour::static_detour; 21 | /// static_detour! { 22 | /// // The simplest detour 23 | /// static Foo: fn(); 24 | /// 25 | /// // An unsafe public detour with a different calling convention 26 | /// pub static PubFoo: unsafe extern "C" fn(i32) -> i32; 27 | /// 28 | /// // A specific visibility modifier 29 | /// pub(crate) static PubSelf: unsafe extern "C" fn(); 30 | /// } 31 | /// # fn main() { } 32 | /// ``` 33 | #[cfg(feature = "static-detour")] 34 | #[cfg_attr(docsrs, doc(cfg(feature = "static-detour")))] 35 | #[macro_export] 36 | // Inspired by: https://github.com/Jascha-N/minhook-rs 37 | macro_rules! static_detour { 38 | // 1 — meta attributes 39 | (@parse_attributes ($($input:tt)*) | #[$attribute:meta] $($rest:tt)*) => { 40 | $crate::static_detour!(@parse_attributes ($($input)* $attribute) | $($rest)*); 41 | }; 42 | (@parse_attributes ($($input:tt)*) | $($rest:tt)+) => { 43 | $crate::static_detour!(@parse_access_modifier (($($input)*)) | $($rest)*); 44 | }; 45 | 46 | // 2 — pub modifier (path/scope/yes/no) 47 | (@parse_access_modifier ($($input:tt)*) | pub(in $vis:path) static $($rest:tt)*) => { 48 | $crate::static_detour!(@parse_name ($($input)* (pub(in $vis))) | $($rest)*); 49 | }; 50 | (@parse_access_modifier ($($input:tt)*) | pub($vis:tt) static $($rest:tt)*) => { 51 | $crate::static_detour!(@parse_name ($($input)* (pub($vis))) | $($rest)*); 52 | }; 53 | (@parse_access_modifier ($($input:tt)*) | pub static $($rest:tt)*) => { 54 | $crate::static_detour!(@parse_name ($($input)* (pub)) | $($rest)*); 55 | }; 56 | (@parse_access_modifier ($($input:tt)*) | static $($rest:tt)*) => { 57 | $crate::static_detour!(@parse_name ($($input)* ()) | $($rest)*); 58 | }; 59 | 60 | // 3 — detour name 61 | (@parse_name ($($input:tt)*) | $name:ident : $($rest:tt)*) => { 62 | $crate::static_detour!(@parse_unsafe ($($input)* ($name)) | $($rest)*); 63 | }; 64 | 65 | // 4 — unsafe modifier (yes/no) 66 | (@parse_unsafe ($($input:tt)*) | unsafe $($rest:tt)*) => { 67 | $crate::static_detour!(@parse_calling_convention ($($input)*) (unsafe) | $($rest)*); 68 | }; 69 | (@parse_unsafe ($($input:tt)*) | $($rest:tt)*) => { 70 | $crate::static_detour!(@parse_calling_convention ($($input)*) () | $($rest)*); 71 | }; 72 | 73 | // 5 — calling convention (extern "XXX"/extern/-) 74 | (@parse_calling_convention 75 | ($($input:tt)*) ($($modifier:tt)*) | extern $cc:tt fn $($rest:tt)*) => { 76 | $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)* extern $cc)) | $($rest)*); 77 | }; 78 | (@parse_calling_convention 79 | ($($input:tt)*) ($($modifier:tt)*) | extern fn $($rest:tt)*) => { 80 | $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)* extern)) | $($rest)*); 81 | }; 82 | (@parse_calling_convention ($($input:tt)*) ($($modifier:tt)*) | fn $($rest:tt)*) => { 83 | $crate::static_detour!(@parse_prototype ($($input)* ($($modifier)*)) | $($rest)*); 84 | }; 85 | 86 | // 6 — argument and return type (return/void) 87 | (@parse_prototype 88 | ($($input:tt)*) | ($($argument_type:ty),* $(,)?) -> $return_type:ty ; $($rest:tt)*) => { 89 | $crate::static_detour!( 90 | @parse_terminator ($($input)* ($($argument_type)*) ($return_type)) | ; $($rest)*); 91 | }; 92 | (@parse_prototype ($($input:tt)*) | ($($argument_type:ty),* $(,)?) $($rest:tt)*) => { 93 | $crate::static_detour!(@parse_terminator ($($input)* ($($argument_type)*) (())) | $($rest)*); 94 | }; 95 | 96 | // 7 — semicolon terminator 97 | (@parse_terminator ($($input:tt)*) | ; $($rest:tt)*) => { 98 | $crate::static_detour!(@parse_entries ($($input)*) | $($rest)*); 99 | }; 100 | 101 | // 8 - additional detours (multiple/single) 102 | (@parse_entries ($($input:tt)*) | $($rest:tt)+) => { 103 | $crate::static_detour!(@aggregate $($input)*); 104 | $crate::static_detour!($($rest)*); 105 | }; 106 | (@parse_entries ($($input:tt)*) | ) => { 107 | $crate::static_detour!(@aggregate $($input)*); 108 | }; 109 | 110 | // 9 - aggregate data for the generate function 111 | (@aggregate ($($attribute:meta)*) ($($visibility:tt)*) ($name:ident) 112 | ($($modifier:tt)*) ($($argument_type:ty)*) ($return_type:ty)) => { 113 | $crate::static_detour!(@argument_names (create_detour)( 114 | ($($attribute)*) ($($visibility)*) ($name) 115 | ($($modifier)*) ($($argument_type)*) ($return_type) 116 | ($($modifier)* fn ($($argument_type),*) -> $return_type) 117 | )($($argument_type)*)); 118 | }; 119 | 120 | // 10 - detour type implementation 121 | (@create_detour ($($argument_name:ident)*) ($($attribute:meta)*) ($($visibility:tt)*) 122 | ($name:ident) ($($modifier:tt)*) ($($argument_type:ty)*) 123 | ($return_type:ty) ($fn_type:ty)) => { 124 | $crate::static_detour!(@generate 125 | #[allow(non_upper_case_globals)] 126 | $(#[$attribute])* 127 | $($visibility)* static $name: $crate::StaticDetour<$fn_type> = { 128 | #[inline(never)] 129 | #[allow(unused_unsafe)] 130 | $($modifier) * fn __ffi_detour( 131 | $($argument_name: $argument_type),*) -> $return_type { 132 | #[allow(unused_unsafe)] 133 | ($name.__detour())($($argument_name),*) 134 | } 135 | 136 | $crate::StaticDetour::__new(__ffi_detour) 137 | }; 138 | ); 139 | }; 140 | 141 | // Associates each argument type with a dummy name. 142 | (@argument_names ($label:ident) ($($input:tt)*) ($($token:tt)*)) => { 143 | $crate::static_detour!(@argument_names ($label) ($($input)*)( 144 | __arg_0 __arg_1 __arg_2 __arg_3 __arg_4 __arg_5 __arg_6 145 | __arg_7 __arg_8 __arg_9 __arg_10 __arg_11 __arg_12 __arg_13 146 | __arg_14 __arg_15 __arg_16 __arg_17 __arg_18 __arg_19 __arg_20 147 | __arg_21 __arg_22 __arg_23 __arg_24 __arg_25 148 | )($($token)*)()); 149 | }; 150 | (@argument_names 151 | ($label:ident) 152 | ($($input:tt)*) 153 | ($hd_name:tt $($tl_name:tt)*) 154 | ($hd:tt $($tl:tt)*) ($($acc:tt)*)) => { 155 | $crate::static_detour!( 156 | @argument_names ($label) ($($input)*) ($($tl_name)*) ($($tl)*) ($($acc)* $hd_name)); 157 | }; 158 | (@argument_names ($label:ident) ($($input:tt)*) ($($name:tt)*) () ($($acc:tt)*)) => { 159 | $crate::static_detour!(@$label ($($acc)*) $($input)*); 160 | }; 161 | 162 | (@generate $item:item) => { $item }; 163 | 164 | // Bootstrapper 165 | ($($t:tt)+) => { 166 | $crate::static_detour!(@parse_attributes () | $($t)+); 167 | }; 168 | } 169 | 170 | macro_rules! impl_hookable { 171 | (@recurse () ($($nm:ident : $ty:ident),*)) => { 172 | impl_hookable!(@impl_all ($($nm : $ty),*)); 173 | }; 174 | (@recurse 175 | ($hd_nm:ident : $hd_ty:ident $(, $tl_nm:ident : $tl_ty:ident)*) 176 | ($($nm:ident : $ty:ident),*)) => { 177 | impl_hookable!(@impl_all ($($nm : $ty),*)); 178 | impl_hookable!(@recurse ($($tl_nm : $tl_ty),*) ($($nm : $ty,)* $hd_nm : $hd_ty)); 179 | }; 180 | 181 | (@impl_all ($($nm:ident : $ty:ident),*)) => { 182 | impl_hookable!(@impl_pair ($($nm : $ty),*) ( fn($($ty),*) -> Ret)); 183 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "cdecl" fn($($ty),*) -> Ret)); 184 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "stdcall" fn($($ty),*) -> Ret)); 185 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "fastcall" fn($($ty),*) -> Ret)); 186 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "win64" fn($($ty),*) -> Ret)); 187 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "C" fn($($ty),*) -> Ret)); 188 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "system" fn($($ty),*) -> Ret)); 189 | 190 | #[cfg(feature = "thiscall-abi")] 191 | #[cfg_attr(docsrs, doc(cfg(feature = "thiscall-abi")))] 192 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "thiscall" fn($($ty),*) -> Ret)); 193 | }; 194 | 195 | (@impl_pair ($($nm:ident : $ty:ident),*) ($($fn_t:tt)*)) => { 196 | impl_hookable!(@impl_fun ($($nm : $ty),*) ($($fn_t)*) (unsafe $($fn_t)*)); 197 | }; 198 | 199 | (@impl_fun ($($nm:ident : $ty:ident),*) ($safe_type:ty) ($unsafe_type:ty)) => { 200 | impl_hookable!(@impl_core ($($nm : $ty),*) ($safe_type)); 201 | impl_hookable!(@impl_core ($($nm : $ty),*) ($unsafe_type)); 202 | 203 | impl_hookable!(@impl_unsafe ($($nm : $ty),*) ($unsafe_type) ($safe_type)); 204 | impl_hookable!(@impl_safe ($($nm : $ty),*) ($safe_type)); 205 | }; 206 | 207 | (@impl_unsafe ($($nm:ident : $ty:ident),*) ($target:ty) ($detour:ty)) => { 208 | #[cfg(feature = "static-detour")] 209 | impl $crate::StaticDetour<$target> { 210 | #[doc(hidden)] 211 | pub unsafe fn call(&self, $($nm : $ty),*) -> Ret { 212 | let original: $target = ::std::mem::transmute(self.trampoline().expect("calling detour trampoline")); 213 | original($($nm),*) 214 | } 215 | } 216 | 217 | impl $crate::GenericDetour<$target> { 218 | #[doc(hidden)] 219 | pub unsafe fn call(&self, $($nm : $ty),*) -> Ret { 220 | let original: $target = ::std::mem::transmute(self.trampoline()); 221 | original($($nm),*) 222 | } 223 | } 224 | }; 225 | 226 | (@impl_safe ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => { 227 | #[cfg(feature = "static-detour")] 228 | impl $crate::StaticDetour<$fn_type> { 229 | #[doc(hidden)] 230 | pub fn call(&self, $($nm : $ty),*) -> Ret { 231 | unsafe { 232 | let original: $fn_type = ::std::mem::transmute(self.trampoline().expect("calling detour trampoline")); 233 | original($($nm),*) 234 | } 235 | } 236 | } 237 | 238 | impl $crate::GenericDetour<$fn_type> { 239 | #[doc(hidden)] 240 | pub fn call(&self, $($nm : $ty),*) -> Ret { 241 | unsafe { 242 | let original: $fn_type = ::std::mem::transmute(self.trampoline()); 243 | original($($nm),*) 244 | } 245 | } 246 | } 247 | }; 248 | 249 | (@impl_core ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => { 250 | unsafe impl Function for $fn_type { 251 | type Arguments = ($($ty,)*); 252 | type Output = Ret; 253 | 254 | unsafe fn from_ptr(ptr: *const ()) -> Self { 255 | ::std::mem::transmute(ptr) 256 | } 257 | 258 | fn to_ptr(&self) -> *const () { 259 | *self as *const () 260 | } 261 | } 262 | }; 263 | 264 | ($($nm:ident : $ty:ident),*) => { 265 | impl_hookable!(@recurse ($($nm : $ty),*) ()); 266 | }; 267 | } 268 | -------------------------------------------------------------------------------- /src/pic/emitter.rs: -------------------------------------------------------------------------------- 1 | use super::Thunkable; 2 | 3 | /// An interface for generating PIC. 4 | pub struct CodeEmitter { 5 | thunks: Vec>, 6 | } 7 | 8 | /// Used for combining PIC segments. 9 | impl CodeEmitter { 10 | /// Constructs a new code emitter. 11 | pub fn new() -> Self { 12 | CodeEmitter { thunks: Vec::new() } 13 | } 14 | 15 | /// Generates code for use at the specified address. 16 | pub fn emit(&self, base: *const ()) -> Vec { 17 | let mut result = Vec::with_capacity(self.len()); 18 | let mut base = base as usize; 19 | 20 | for thunk in &self.thunks { 21 | // Retrieve the code for the segment 22 | let code = thunk.generate(base); 23 | assert_eq!(code.len(), thunk.len()); 24 | 25 | // Advance the current EIP address 26 | base += thunk.len(); 27 | result.extend(code); 28 | } 29 | 30 | result 31 | } 32 | 33 | /// Adds a position-independant code segment. 34 | pub fn add_thunk(&mut self, thunk: Box) { 35 | self.thunks.push(thunk); 36 | } 37 | 38 | /// Returns the total size of a all code segments. 39 | pub fn len(&self) -> usize { 40 | self.thunks.iter().fold(0, |sum, thunk| sum + thunk.len()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pic/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::emitter::CodeEmitter; 2 | pub use self::thunk::{FixedThunk, UnsafeThunk}; 3 | 4 | mod emitter; 5 | mod thunk; 6 | 7 | /// An interface for generating PIC thunks. 8 | pub trait Thunkable { 9 | /// Generates the code at the specified address. 10 | fn generate(&self, address: usize) -> Vec; 11 | 12 | /// Returns the size of a generated thunk. 13 | fn len(&self) -> usize; 14 | } 15 | 16 | /// Thunkable implementation for static data 17 | impl Thunkable for Vec { 18 | /// Generates a static thunk assumed to be PIC 19 | fn generate(&self, _address: usize) -> Vec { 20 | self.clone() 21 | } 22 | 23 | /// Returns the size of a generated thunk 24 | fn len(&self) -> usize { 25 | self.len() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pic/thunk.rs: -------------------------------------------------------------------------------- 1 | use super::Thunkable; 2 | use generic_array::{ArrayLength, GenericArray}; 3 | 4 | /// A closure that generates a thunk. 5 | pub struct FixedThunk>(Box GenericArray>); 6 | 7 | impl> FixedThunk { 8 | /// Constructs a new thunk with a specific closure. 9 | pub fn new GenericArray + 'static>(callback: T) -> Self { 10 | FixedThunk(Box::new(callback)) 11 | } 12 | } 13 | 14 | /// Thunks implement the thunkable interface. 15 | impl> Thunkable for FixedThunk { 16 | fn generate(&self, address: usize) -> Vec { 17 | self.0(address).to_vec() 18 | } 19 | 20 | fn len(&self) -> usize { 21 | N::to_usize() 22 | } 23 | } 24 | 25 | /// A closure that generates an unsafe thunk. 26 | pub struct UnsafeThunk { 27 | callback: Box Vec>, 28 | size: usize, 29 | } 30 | 31 | /// An unsafe thunk, because it cannot be asserted at compile time, that the 32 | /// generated data is the same size as `len()` (will panic otherwise when 33 | /// emitted). 34 | impl UnsafeThunk { 35 | /// Constructs a new dynamic thunk with a closure. 36 | pub unsafe fn new Vec + 'static>(callback: T, size: usize) -> Self { 37 | UnsafeThunk { 38 | callback: Box::new(callback), 39 | size, 40 | } 41 | } 42 | } 43 | 44 | impl Thunkable for UnsafeThunk { 45 | /// Generates a dynamic thunk, assumed to be PIC. 46 | fn generate(&self, address: usize) -> Vec { 47 | (self.callback)(address) 48 | } 49 | 50 | /// Returns the size of the generated thunk. 51 | fn len(&self) -> usize { 52 | self.size 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Traits describing detours and applicable functions. 2 | //! 3 | //! Several of the traits in this module are automatically implemented and 4 | //! should generally not be implemented by users of this library. 5 | 6 | /// Trait representing a function that can be used as a target or detour for 7 | /// detouring. 8 | pub unsafe trait Function: Sized + Copy + Sync + 'static { 9 | /// The argument types as a tuple. 10 | type Arguments; 11 | 12 | /// The return type. 13 | type Output; 14 | 15 | /// Constructs a `Function` from an untyped pointer. 16 | unsafe fn from_ptr(ptr: *const ()) -> Self; 17 | 18 | /// Returns an untyped pointer for this function. 19 | fn to_ptr(&self) -> *const (); 20 | } 21 | 22 | /// Trait indicating that `Self` can be detoured by the given function `D`. 23 | pub unsafe trait HookableWith: Function {} 24 | 25 | unsafe impl HookableWith for T {} 26 | 27 | #[cfg(not(feature = "28-args"))] 28 | impl_hookable! { 29 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 30 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L, __arg_12: M, __arg_13: N 31 | } 32 | 33 | #[cfg(all(feature = "28-args", not(feature = "42-args")))] 34 | impl_hookable! { 35 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 36 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L, __arg_12: M, __arg_13: N, 37 | __arg_14: O, __arg_15: P, __arg_16: Q, __arg_17: R, __arg_18: S, __arg_19: T, __arg_20: U, 38 | __arg_21: V, __arg_22: W, __arg_23: X, __arg_24: Y, __arg_25: Z, __arg_26: AA, __arg27: AB 39 | } 40 | 41 | #[cfg(feature = "42-args")] 42 | impl_hookable! { 43 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 44 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L, __arg_12: M, __arg_13: N, 45 | __arg_14: O, __arg_15: P, __arg_16: Q, __arg_17: R, __arg_18: S, __arg_19: T, __arg_20: U, 46 | __arg_21: V, __arg_22: W, __arg_23: X, __arg_24: Y, __arg_25: Z, __arg_26: AA, __arg_27: AB, 47 | __arg_28: AC, __arg_29: AD, __arg_30: AE, __arg_31: AF, __arg_32: AG, __arg_33: AH, __arg_34: AI, 48 | __arg_35: AJ, __arg_36: AK, __arg_37: AL, __arg_38: AM, __arg_39: AN, __arg_40: AO, __arg_41: AP 49 | } 50 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | 3 | /// Returns true if an address is executable. 4 | pub fn is_executable_address(address: *const ()) -> Result { 5 | Ok( 6 | region::query(address as *const _)? 7 | .protection() 8 | .contains(region::Protection::EXECUTE), 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | use retour::Result; 2 | use std::mem; 3 | 4 | type FnAdd = extern "C" fn(i32, i32) -> i32; 5 | 6 | #[inline(never)] 7 | extern "C" fn sub_detour(x: i32, y: i32) -> i32 { 8 | unsafe { std::ptr::read_volatile(&x as *const i32) - y } 9 | } 10 | 11 | 12 | mod raw { 13 | use super::*; 14 | use retour::RawDetour; 15 | 16 | #[test] 17 | fn test() -> Result<()> { 18 | #[inline(never)] 19 | extern "C" fn add(x: i32, y: i32) -> i32 { 20 | unsafe { std::ptr::read_volatile(&x as *const i32) + y } 21 | } 22 | 23 | unsafe { 24 | let hook = RawDetour::new(add as *const (), sub_detour as *const ()) 25 | .expect("target or source is not usable for detouring"); 26 | 27 | assert_eq!(add(10, 5), 15); 28 | assert!(!hook.is_enabled()); 29 | 30 | hook.enable()?; 31 | { 32 | assert!(hook.is_enabled()); 33 | 34 | // The `add` function is hooked, but can be called using the trampoline 35 | let trampoline: FnAdd = mem::transmute(hook.trampoline()); 36 | 37 | // Call the original function 38 | assert_eq!(trampoline(10, 5), 15); 39 | 40 | // Call the hooked function (i.e `add → sub_detour`) 41 | assert_eq!(add(10, 5), 5); 42 | } 43 | hook.disable()?; 44 | 45 | // With the hook disabled, the function is restored 46 | assert!(!hook.is_enabled()); 47 | assert_eq!(add(10, 5), 15); 48 | } 49 | Ok(()) 50 | } 51 | } 52 | 53 | mod generic { 54 | use super::*; 55 | use retour::GenericDetour; 56 | 57 | #[test] 58 | fn test() -> Result<()> { 59 | #[inline(never)] 60 | extern "C" fn add(x: i32, y: i32) -> i32 { 61 | unsafe { std::ptr::read_volatile(&x as *const i32) + y } 62 | } 63 | 64 | unsafe { 65 | let hook = GenericDetour::::new(add, sub_detour) 66 | .expect("target or source is not usable for detouring"); 67 | 68 | assert_eq!(add(10, 5), 15); 69 | assert_eq!(hook.call(10, 5), 15); 70 | hook.enable()?; 71 | { 72 | assert_eq!(hook.call(10, 5), 15); 73 | assert_eq!(add(10, 5), 5); 74 | } 75 | hook.disable()?; 76 | assert_eq!(hook.call(10, 5), 15); 77 | assert_eq!(add(10, 5), 15); 78 | } 79 | Ok(()) 80 | } 81 | } 82 | 83 | #[cfg(feature = "static-detour")] 84 | mod statik { 85 | use super::*; 86 | use retour::static_detour; 87 | 88 | #[inline(never)] 89 | unsafe extern "C" fn add(x: i32, y: i32) -> i32 { 90 | std::ptr::read_volatile(&x as *const i32) + y 91 | } 92 | 93 | static_detour! { 94 | #[doc="Test with attributes"] 95 | pub static DetourAdd: unsafe extern "C" fn(i32, i32) -> i32; 96 | } 97 | 98 | #[test] 99 | fn test() -> Result<()> { 100 | unsafe { 101 | DetourAdd.initialize(add, |x, y| x - y)?; 102 | 103 | assert_eq!(add(10, 5), 15); 104 | assert_eq!(DetourAdd.is_enabled(), false); 105 | 106 | DetourAdd.enable()?; 107 | { 108 | assert!(DetourAdd.is_enabled()); 109 | assert_eq!(DetourAdd.call(10, 5), 15); 110 | assert_eq!(add(10, 5), 5); 111 | } 112 | DetourAdd.disable()?; 113 | 114 | assert_eq!(DetourAdd.is_enabled(), false); 115 | assert_eq!(DetourAdd.call(10, 5), 15); 116 | assert_eq!(add(10, 5), 15); 117 | } 118 | Ok(()) 119 | } 120 | } 121 | 122 | #[cfg(feature = "28-args")] 123 | mod args_28 { 124 | use super::*; 125 | use retour::GenericDetour; 126 | 127 | 128 | type I = i32; 129 | type BigFn = fn(I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I); 130 | 131 | fn a(_: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I) {} 132 | fn b(_: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I) {} 133 | #[test] 134 | fn sanity_check() -> Result<()> { 135 | let hook = unsafe { GenericDetour::::new(a, b) }; 136 | Ok(()) 137 | } 138 | } 139 | #[cfg(feature = "42-args")] 140 | mod args_42 { 141 | use super::*; 142 | use retour::GenericDetour; 143 | 144 | 145 | type I = i32; 146 | type BiggerFn = fn(I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I); 147 | 148 | fn a(_: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, 149 | _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I) {} 150 | fn b(_: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, 151 | _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I, _: I) {} 152 | #[test] 153 | fn sanity_check() -> Result<()> { 154 | let hook = unsafe { GenericDetour::::new(a, b)? }; 155 | Ok(()) 156 | } 157 | } 158 | 159 | #[cfg(target_arch="x86_64")] 160 | mod relative_ip { 161 | use std::arch::global_asm; 162 | use super::*; 163 | use retour::GenericDetour; 164 | 165 | static VALUE: i32 = 3; 166 | 167 | global_asm!(r#" 168 | .global check_value 169 | check_value: 170 | cmp dword ptr [rip - {value}], 5 // 83 3D XX XX XX XX 05 // XX - displacement bytes 171 | setz al // 0F 94 C0 172 | and al, 1 // 24 01 173 | ret // C3 174 | "#, 175 | value = sym VALUE, 176 | ); 177 | 178 | type FnCheckValue = extern "C" fn() -> bool; 179 | 180 | unsafe extern "C" { 181 | safe fn check_value() -> bool; 182 | } 183 | 184 | extern fn new_check_value() -> bool { 185 | true 186 | } 187 | 188 | #[test] 189 | fn test() -> Result<()> { 190 | unsafe { 191 | let hook = GenericDetour::::new(check_value, new_check_value) 192 | .expect("target or source is not usable for detouring"); 193 | 194 | assert_eq!(check_value(), false); 195 | assert_eq!(hook.call(), false); 196 | hook.enable()?; 197 | { 198 | assert_eq!(hook.call(), false); 199 | assert_eq!(check_value(), true); 200 | } 201 | hook.disable()?; 202 | assert_eq!(hook.call(), false); 203 | assert_eq!(check_value(), false); 204 | } 205 | 206 | Ok(()) 207 | } 208 | } 209 | --------------------------------------------------------------------------------