├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── rp-rs.rs ├── rustfmt.toml ├── src ├── common.rs ├── cpu │ ├── arm.rs │ ├── mod.rs │ └── x86.rs ├── engine.rs ├── error.rs ├── format │ ├── elf.rs │ ├── mach.rs │ ├── mod.rs │ └── pe.rs ├── gadget.rs ├── lib.rs ├── section.rs └── session.rs └── tests └── bin ├── big-arm32.elf ├── big-arm32.pe ├── big-arm64.elf ├── big-arm64.pe ├── big-x64.elf ├── big-x64.pe ├── big-x86.elf ├── big-x86.pe ├── small-arm32.elf ├── small-arm32.pe ├── small-arm64.elf ├── small-arm64.pe ├── small-x64.elf ├── small-x64.macho ├── small-x64.pe ├── small-x86.elf ├── small-x86.macho └── small-x86.pe /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.pe binary 3 | *.elf binary 4 | *.macho binary 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | workflow_dispatch: 9 | 10 | env: 11 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 12 | PROJECT_NAME: "ropgadget-rs" 13 | REPO: hugsy/ropgadget-rs 14 | VERBOSE: 1 15 | RUST_BACKTRACE: 1 16 | DEBUG_IN_CI: 0 17 | 18 | jobs: 19 | build: 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | job: 24 | - { os: ubuntu-latest, target: arm-unknown-linux-gnueabihf , use-cross: true , name: "linux_armv7"} 25 | - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu , use-cross: true , name: "linux_arm64"} 26 | - { os: ubuntu-latest, target: i686-unknown-linux-gnu , use-cross: true , name: "linux_x86"} 27 | - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu , use-cross: true , name: "linux_x64"} 28 | - { os: macos-latest, target: x86_64-apple-darwin , use-cross: false, name: "macos_x64"} 29 | - { os: windows-latest, target: aarch64-pc-windows-msvc , use-cross: false, name: "windows_arm64"} 30 | - { os: windows-latest, target: i686-pc-windows-msvc , use-cross: false, name: "windows_x86"} 31 | - { os: windows-latest, target: x86_64-pc-windows-msvc , use-cross: false, name: "windows_x64"} 32 | 33 | name: "${{ matrix.job.os }} / ${{ matrix.job.target }}" 34 | runs-on: ${{ matrix.job.os }} 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4.1.1 39 | 40 | - name: Install prerequisites 41 | shell: bash 42 | run: | 43 | case ${{ matrix.job.target }} in 44 | arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; 45 | aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; 46 | esac 47 | 48 | - name: Install Rust toolchain 49 | run: | 50 | rustup set profile minimal 51 | rustup toolchain install nightly 52 | rustup override set stable 53 | rustup target add ${{ matrix.job.target }} 54 | 55 | - name: Show version information (Rust, cargo, GCC) 56 | shell: bash 57 | run: | 58 | gcc --version || true 59 | rustup -V 60 | rustup toolchain list 61 | rustup default 62 | cargo -V 63 | rustc -V 64 | 65 | - name: Build (non-Windows) 66 | if: ${{ matrix.job.os != 'windows-latest' }} 67 | run: | 68 | case ${{ matrix.job.use-cross }} in 69 | true) 70 | cargo install cross 71 | cross build --release --all-targets --target=${{ matrix.job.target }} 72 | ;; 73 | 74 | false) 75 | cargo build --release --all-targets --target=${{ matrix.job.target }} 76 | ;; 77 | 78 | *) 79 | echo "Unknown ${{ matrix.job.use-cross }}" 80 | exit 1 81 | ;; 82 | esac 83 | 84 | - name: Build (Windows) 85 | if: ${{ matrix.job.os == 'windows-latest' }} 86 | run: | 87 | cargo build --release --all-targets --target=${{ matrix.job.target }} 88 | 89 | - name: Run Interactive CI Debugger 90 | if: ${{ matrix.job.os == 'macos-latest' && env.DEBUG_IN_CI == '1' }} 91 | run: curl -sSf https://sshx.io/get | sh -s run 92 | 93 | 94 | - name: Publish artifact 95 | uses: actions/upload-artifact@v4.3.1 96 | with: 97 | name: ${{ env.PROJECT_NAME}}_${{ matrix.job.name }}_${{ github.sha }} 98 | path: | 99 | target/release/examples/rp-rs* 100 | target/release/libropgadget_rs.* 101 | target/${{ matrix.job.target }}/release/libropgadget_rs.* 102 | target/${{ matrix.job.target }}/release/examples/rp-rs* 103 | 104 | notify: 105 | runs-on: ubuntu-latest 106 | needs: build 107 | steps: 108 | - name: Send Discord notification 109 | env: 110 | COMMIT_URL: "https://github.com/${{ env.REPO }}/commit/${{ github.sha }}" 111 | RUN_URL: "https://github.com/${{ env.REPO }}/actions/runs/${{ github.run_id }}" 112 | BRANCH_URL: "https://github.com/${{ env.REPO }}/tree/${{ github.ref_name }}" 113 | AUTHOR_URL: "https://github.com/${{ github.actor }}" 114 | AVATAR_URL: "https://camo.githubusercontent.com/9ddce666945f8c507d7c9a83aaa0518b36a47fa1fd9e823e061ed4753e7becdc/68747470733a2f2f692e696d6775722e636f6d2f7a6a63787956662e706e67" 115 | uses: sarisia/actions-status-discord@v1.14.0 116 | with: 117 | nodetail: true 118 | title: 🚧 Build `${{ github.sha }}` for `${{ env.REPO }}` 🚧 119 | description: | 120 | [Job #${{ github.run_number }}](${{ env.RUN_URL }}): CI build `${{ github.sha }}` initiated by [${{ github.actor }}](${{ env.AUTHOR_URL }}) was successful 121 | 122 | color: 0x00ff00 123 | username: ${{ github.actor }} via GithubBot 124 | avatar_url: ${{ env.AVATAR_URL }} 125 | 126 | test: 127 | runs-on: ubuntu-latest 128 | needs: build 129 | steps: 130 | - uses: actions/checkout@v4.1.1 131 | - run: cargo fmt 132 | - run: cargo check 133 | - run: cargo test 134 | 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .vscode/ 13 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi 0.1.19", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "bitflags" 18 | version = "1.3.2" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "2.4.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 27 | 28 | [[package]] 29 | name = "capstone" 30 | version = "0.12.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b08ca438d9585a2b216b0c2e88ea51e096286c5f197f7be2526bb515ef775b6c" 33 | dependencies = [ 34 | "capstone-sys", 35 | "libc", 36 | ] 37 | 38 | [[package]] 39 | name = "capstone-sys" 40 | version = "0.16.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "fe7183271711ffb7c63a6480e4baf480e0140da59eeba9b18fcc8bf3478950e3" 43 | dependencies = [ 44 | "cc", 45 | "libc", 46 | ] 47 | 48 | [[package]] 49 | name = "cc" 50 | version = "1.0.78" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "clap" 62 | version = "4.0.29" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" 65 | dependencies = [ 66 | "bitflags 1.3.2", 67 | "clap_derive", 68 | "clap_lex", 69 | "is-terminal", 70 | "once_cell", 71 | "strsim", 72 | "termcolor", 73 | ] 74 | 75 | [[package]] 76 | name = "clap_derive" 77 | version = "4.0.21" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" 80 | dependencies = [ 81 | "heck", 82 | "proc-macro-error", 83 | "proc-macro2", 84 | "quote", 85 | "syn 1.0.105", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.3.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 93 | dependencies = [ 94 | "os_str_bytes", 95 | ] 96 | 97 | [[package]] 98 | name = "colored" 99 | version = "2.0.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 102 | dependencies = [ 103 | "atty", 104 | "lazy_static", 105 | "winapi", 106 | ] 107 | 108 | [[package]] 109 | name = "errno" 110 | version = "0.2.8" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 113 | dependencies = [ 114 | "errno-dragonfly", 115 | "libc", 116 | "winapi", 117 | ] 118 | 119 | [[package]] 120 | name = "errno-dragonfly" 121 | version = "0.1.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 124 | dependencies = [ 125 | "cc", 126 | "libc", 127 | ] 128 | 129 | [[package]] 130 | name = "goblin" 131 | version = "0.8.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887" 134 | dependencies = [ 135 | "log", 136 | "plain", 137 | "scroll", 138 | ] 139 | 140 | [[package]] 141 | name = "heck" 142 | version = "0.4.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 145 | 146 | [[package]] 147 | name = "hermit-abi" 148 | version = "0.1.19" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 151 | dependencies = [ 152 | "libc", 153 | ] 154 | 155 | [[package]] 156 | name = "hermit-abi" 157 | version = "0.2.6" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 160 | dependencies = [ 161 | "libc", 162 | ] 163 | 164 | [[package]] 165 | name = "io-lifetimes" 166 | version = "1.0.3" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" 169 | dependencies = [ 170 | "libc", 171 | "windows-sys", 172 | ] 173 | 174 | [[package]] 175 | name = "is-terminal" 176 | version = "0.4.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" 179 | dependencies = [ 180 | "hermit-abi 0.2.6", 181 | "io-lifetimes", 182 | "rustix", 183 | "windows-sys", 184 | ] 185 | 186 | [[package]] 187 | name = "lazy_static" 188 | version = "1.4.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 191 | 192 | [[package]] 193 | name = "libc" 194 | version = "0.2.138" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 197 | 198 | [[package]] 199 | name = "linux-raw-sys" 200 | version = "0.1.4" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 203 | 204 | [[package]] 205 | name = "log" 206 | version = "0.4.17" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 209 | dependencies = [ 210 | "cfg-if", 211 | ] 212 | 213 | [[package]] 214 | name = "once_cell" 215 | version = "1.16.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 218 | 219 | [[package]] 220 | name = "os_str_bytes" 221 | version = "6.4.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 224 | 225 | [[package]] 226 | name = "plain" 227 | version = "0.2.3" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 230 | 231 | [[package]] 232 | name = "proc-macro-error" 233 | version = "1.0.4" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 236 | dependencies = [ 237 | "proc-macro-error-attr", 238 | "proc-macro2", 239 | "quote", 240 | "syn 1.0.105", 241 | "version_check", 242 | ] 243 | 244 | [[package]] 245 | name = "proc-macro-error-attr" 246 | version = "1.0.4" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 249 | dependencies = [ 250 | "proc-macro2", 251 | "quote", 252 | "version_check", 253 | ] 254 | 255 | [[package]] 256 | name = "proc-macro2" 257 | version = "1.0.78" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 260 | dependencies = [ 261 | "unicode-ident", 262 | ] 263 | 264 | [[package]] 265 | name = "quote" 266 | version = "1.0.35" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 269 | dependencies = [ 270 | "proc-macro2", 271 | ] 272 | 273 | [[package]] 274 | name = "ropgadget-rs" 275 | version = "0.4.0" 276 | dependencies = [ 277 | "bitflags 2.4.2", 278 | "capstone", 279 | "clap", 280 | "colored", 281 | "goblin", 282 | "log", 283 | ] 284 | 285 | [[package]] 286 | name = "rustix" 287 | version = "0.36.5" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" 290 | dependencies = [ 291 | "bitflags 1.3.2", 292 | "errno", 293 | "io-lifetimes", 294 | "libc", 295 | "linux-raw-sys", 296 | "windows-sys", 297 | ] 298 | 299 | [[package]] 300 | name = "scroll" 301 | version = "0.12.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 304 | dependencies = [ 305 | "scroll_derive", 306 | ] 307 | 308 | [[package]] 309 | name = "scroll_derive" 310 | version = "0.12.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" 313 | dependencies = [ 314 | "proc-macro2", 315 | "quote", 316 | "syn 2.0.48", 317 | ] 318 | 319 | [[package]] 320 | name = "strsim" 321 | version = "0.10.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 324 | 325 | [[package]] 326 | name = "syn" 327 | version = "1.0.105" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" 330 | dependencies = [ 331 | "proc-macro2", 332 | "quote", 333 | "unicode-ident", 334 | ] 335 | 336 | [[package]] 337 | name = "syn" 338 | version = "2.0.48" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 341 | dependencies = [ 342 | "proc-macro2", 343 | "quote", 344 | "unicode-ident", 345 | ] 346 | 347 | [[package]] 348 | name = "termcolor" 349 | version = "1.1.3" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 352 | dependencies = [ 353 | "winapi-util", 354 | ] 355 | 356 | [[package]] 357 | name = "unicode-ident" 358 | version = "1.0.5" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 361 | 362 | [[package]] 363 | name = "version_check" 364 | version = "0.9.4" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 367 | 368 | [[package]] 369 | name = "winapi" 370 | version = "0.3.9" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 373 | dependencies = [ 374 | "winapi-i686-pc-windows-gnu", 375 | "winapi-x86_64-pc-windows-gnu", 376 | ] 377 | 378 | [[package]] 379 | name = "winapi-i686-pc-windows-gnu" 380 | version = "0.4.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 383 | 384 | [[package]] 385 | name = "winapi-util" 386 | version = "0.1.5" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 389 | dependencies = [ 390 | "winapi", 391 | ] 392 | 393 | [[package]] 394 | name = "winapi-x86_64-pc-windows-gnu" 395 | version = "0.4.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 398 | 399 | [[package]] 400 | name = "windows-sys" 401 | version = "0.42.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 404 | dependencies = [ 405 | "windows_aarch64_gnullvm", 406 | "windows_aarch64_msvc", 407 | "windows_i686_gnu", 408 | "windows_i686_msvc", 409 | "windows_x86_64_gnu", 410 | "windows_x86_64_gnullvm", 411 | "windows_x86_64_msvc", 412 | ] 413 | 414 | [[package]] 415 | name = "windows_aarch64_gnullvm" 416 | version = "0.42.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 419 | 420 | [[package]] 421 | name = "windows_aarch64_msvc" 422 | version = "0.42.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 425 | 426 | [[package]] 427 | name = "windows_i686_gnu" 428 | version = "0.42.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 431 | 432 | [[package]] 433 | name = "windows_i686_msvc" 434 | version = "0.42.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 437 | 438 | [[package]] 439 | name = "windows_x86_64_gnu" 440 | version = "0.42.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 443 | 444 | [[package]] 445 | name = "windows_x86_64_gnullvm" 446 | version = "0.42.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 449 | 450 | [[package]] 451 | name = "windows_x86_64_msvc" 452 | version = "0.42.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 455 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ropgadget-rs" 3 | version = "0.4.0" 4 | authors = ["hugsy "] 5 | edition = "2018" 6 | description = "Another (bad) ROP gadget finder" 7 | homepage = "https://github.com/hugsy/ropgadget-rs/" 8 | repository = "https://github.com/hugsy/ropgadget-rs/" 9 | readme = "README.md" 10 | license = "MIT" 11 | keywords = [ 12 | "pwn", 13 | "ctf", 14 | "ropgadget", 15 | "rop", 16 | "x64", 17 | "x86", 18 | "arm", 19 | "arm64", 20 | "pe", 21 | "elf", 22 | "macho", 23 | ] 24 | include = ["/Cargo.toml", "/LICENSE", "README.md", "/src/**", "/examples/**"] 25 | 26 | [dependencies] 27 | goblin = "0.8.0" 28 | capstone = "0.12.0" 29 | clap = { version = "4.0.29", features = ["derive"] } 30 | colored = "2" 31 | bitflags = "2.4.2" 32 | log = { version = "0.4.11", features = ["std"] } 33 | 34 | [lib] 35 | crate-type = ["dylib", "rlib"] 36 | 37 | [[example]] 38 | name = "rp-rs" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 crazy rabbidz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 | # `ropgadget-rs` 6 | 7 |

8 | Discord 9 | 10 |

11 | 12 | 13 | RopGadget-rs started as a weekend project to learn [Rust](https://www.rust-lang.org/). But as usual it also started from the need to get really fast & easily portable ropgadget finder capable of handling quickly any binary (especially very large ones such as mshtml, ntoskrnl, chrome, etc.). 14 | 15 | > [!NOTE] 16 | > This library is a side project to learn Rust. If you want better tools, see the ones mentioned at the bottom of the page. 17 | 18 | Currently supports: 19 | 20 | | | ELF | PE | MachO | 21 | | :---: | :----: |:-----:|:---------:| 22 | | x86 | ✅ | ✅ | ✅ | 23 | | x64 | ✅ | ✅ | ✅ | 24 | | arm | ✅ | ✅ | ❌ | 25 | | arm64 | ✅ | ✅ | ❌ | 26 | 27 | 28 | ## `ropgadget-rs` 29 | 30 | Since 0.4, RopGadget-Rs was re-designed to be built as a library so it can be integrated to other projects. 31 | But a lightweight standalone binary that features all what the library offers, can also be built. 32 | 33 | ## Build 34 | 35 | (Optionally) If you don't have `cargo`: 36 | 37 | - On Linux/MacOS 38 | ```bash 39 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 40 | ``` 41 | 42 | - On Windows 43 | ```ps1 44 | Invoke-WebRequest https://win.rustup.rs/x86_64 -UseBasicParsing -OutFile "rustup-init.exe" 45 | Invoke-Expression rustup-init.exe 46 | ``` 47 | 48 | Then build: 49 | ```bash 50 | git clone https://github.com/hugsy/ropgadget-rs 51 | cd ropgadget-rs 52 | cargo build --release --lib 53 | ``` 54 | 55 | You might also want to build the ropgadget-rs binary so it can be easily used from the command line: 56 | ```bash 57 | cargo build --release --example rp-rs 58 | ``` 59 | 60 | 61 | And run: 62 | ```bash 63 | cargo run -- --help 64 | ``` 65 | 66 | 67 | ## Install 68 | 69 | Via `cargo` 70 | 71 | ```bash 72 | cargo install --bins --git https://github.com/hugsy/ropgadget-rs.git 73 | ``` 74 | 75 | ## Performance 76 | 77 | The tool performs decently but could largely be optimized (and will be, over time). 78 | Here are some performance obtained on an old i5-4300M (build in `--release` mode) with 2 threads (default) 79 | 80 | * `ntoskrnl.exe` (Windows 10 RS6 - 10.0.19041.329) - 10,921,280 bytes 81 | 82 | ```console 83 | > ./ropgadget-rs.exe -o rop.txt -vv ./ntoskrnl-rs6.exe 84 | [INFO] - Checking file './ntoskrnl-rs6.exe' 85 | [INFO] - Creating new Session(file=./ntoskrnl-rs6.exe, Info(Arch=x86-64, OS=PE)) 86 | [INFO] - Looking for gadgets in 15 sections (with 2 threads)...' 87 | [INFO] - Dumping 336787 gadgets to 'rop.txt'... 88 | [INFO] - Done! 89 | [INFO] - Execution: 336787 gadgets found in 13.5224138s 90 | ``` 91 | 92 | * `msedge.dll` (Chromium Edge - 83.0.478.64) - 145,665,416 bytes 93 | 94 | ```console 95 | > ./ropgadget-rs -o rop.txt -vv ./msedge.dll 96 | [INFO] - Checking file './msedge.dll' 97 | [INFO] - Creating new Session(file=./msedge.dll, Info(Arch=x86-64, OS=PE)) 98 | [INFO] - Looking for gadgets in 1 sections (with 2 threads)...' 99 | [INFO] - Dumping 5713703 gadgets to 'rop.txt'... 100 | [INFO] - Done! 101 | [INFO] - Execution: 5713703 gadgets found in 132.2237842s 102 | ``` 103 | 104 | YMMV but most small files (like Unix binaries) will execute in way under 1 second. 105 | 106 | ```console 107 | $ ./ropgadget-rs -vv -o /dev/null /bin/ls 108 | [INFO] - Checking file '/bin/ls' 109 | [INFO] - Creating new Session(file=/bin/ls, Info(Arch=x86-64, OS=ELF)) 110 | [INFO] - Looking for gadgets in 5 sections (with 2 threads)...' 111 | [INFO] - Dumping 3544 gadgets to '/dev/null'... 112 | [INFO] - Done! 113 | [INFO] - Execution: 3544 gadgets found in 151.5587ms 114 | ``` 115 | 116 | 117 | ## Better projects 118 | 119 | Unless you're ok with experiencing my bugs, you should probably check out one of those projects: 120 | - [rp++](https://github.com/0vercl0k/rp) 121 | - [ropper](https://github.com/sashs/ropper) 122 | - [RopGadget](https://github.com/JonathanSalwan/ROPgadget) 123 | 124 | -------------------------------------------------------------------------------- /examples/rp-rs.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{ArgAction, Parser}; 4 | use log::{info, LevelFilter}; 5 | 6 | use ropgadget_rs::common::GenericResult; 7 | use ropgadget_rs::cpu; 8 | 9 | use ropgadget_rs::collect_all_gadgets; 10 | use ropgadget_rs::gadget::InstructionGroup; 11 | use ropgadget_rs::session::RopGadgetOutput; 12 | use ropgadget_rs::session::{RopProfileStrategy, Session}; 13 | 14 | #[derive(Parser, Debug)] 15 | #[command(author, version, about, long_about)] // Read from `Cargo.toml` 16 | pub struct Args { 17 | /// The file to parse 18 | #[arg(value_name = "FILE")] 19 | filepath: PathBuf, 20 | 21 | /// The number of threads to use 22 | #[arg(short, long = "number-of-threads", default_value_t = 4)] 23 | thread_num: u8, 24 | 25 | /// Write gadget to file (optional, defaults to stdout) 26 | #[arg(short, long = "output-file", value_name = "OUTPUT")] 27 | output_file: Option, 28 | 29 | /// The verbosity level 30 | #[arg(short, long = "verbose", action = clap::ArgAction::Count)] 31 | verbosity: u8, 32 | 33 | /// Unique gadgets 34 | #[arg(short, long, action = ArgAction::SetTrue)] 35 | unique: bool, 36 | 37 | /// Force the architecture to given value 38 | #[arg(long, value_enum)] 39 | architecture: Option, 40 | 41 | // /// Force the OS to given value 42 | // #[arg(long, value_enum, default_value_t = format::FileFormat::Auto)] 43 | // format: Option, 44 | /// Specify an image base 45 | #[arg(short, long, default_value_t = 0)] 46 | image_base: u32, 47 | 48 | /// Disable colors on output. This option is forced on when writing to file. 49 | #[arg(long)] 50 | no_color: bool, 51 | 52 | /// The maximum number of instructions in a gadget 53 | #[arg(long, default_value_t = 6)] 54 | max_insn_per_gadget: u8, 55 | 56 | /// The maximum size of the gadget 57 | #[arg(long, default_value_t = 32)] 58 | max_size: u8, 59 | 60 | /// The type of gadgets to focus on (default - return only) 61 | #[arg(long, value_enum)] 62 | rop_types: Vec, 63 | 64 | /// The profile type (default - fast) 65 | #[arg(long, value_enum, default_value_t = RopProfileStrategy::Fast)] 66 | profile_type: RopProfileStrategy, 67 | } 68 | 69 | fn main() -> GenericResult<()> { 70 | let args = Args::parse(); 71 | 72 | let verbosity = match args.verbosity { 73 | 4 => LevelFilter::Trace, // -vvvv 74 | 3 => LevelFilter::Debug, // -vvv 75 | 2 => LevelFilter::Info, // -vv 76 | 1 => LevelFilter::Warn, // -v 77 | _ => LevelFilter::Error, 78 | }; 79 | 80 | let _output = match args.output_file { 81 | None => RopGadgetOutput::Console, 82 | Some(fpath) => RopGadgetOutput::File(fpath), 83 | }; 84 | 85 | let sess = Session::new(args.filepath) 86 | .nb_thread(args.thread_num.into()) 87 | .output(_output) 88 | .unique_only(args.unique) 89 | .verbosity(verbosity) 90 | .use_color(!args.no_color); 91 | 92 | info!("Created session: {:?}", sess); 93 | match collect_all_gadgets(sess) { 94 | Ok(_) => Ok(()), 95 | Err(e) => Err(e), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | 3 | pub type GenericResult = Result; 4 | -------------------------------------------------------------------------------- /src/cpu/arm.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu; 2 | 3 | pub struct Arm; 4 | 5 | impl cpu::Cpu for Arm { 6 | fn cpu_type(&self) -> cpu::CpuType { 7 | cpu::CpuType::ARM 8 | } 9 | 10 | fn ptrsize(&self) -> usize { 11 | 4 12 | // TODO: thumb 13 | } 14 | 15 | fn ret_insns(&self) -> Vec<(Vec, Vec)> { 16 | vec![( 17 | vec![0xd6, 0x5f, 0x03, 0xc0].into_iter().rev().collect(), 18 | vec![0xff, 0xff, 0xff, 0xff].into_iter().rev().collect(), 19 | )] 20 | } 21 | 22 | fn call_insns(&self) -> Vec<(Vec, Vec)> { 23 | vec![ 24 | ( 25 | vec![0b1101_0001, 0b0010_1111, 0b1111_1111, 0b0001_0000] 26 | .into_iter() 27 | .rev() 28 | .collect(), 29 | vec![0b1111_1111, 0b1111_1111, 0b1111_1111, 0b1111_0000] 30 | .into_iter() 31 | .rev() 32 | .collect(), 33 | ), // 4.3 Branch and Exchange (BX) 34 | ] 35 | } 36 | 37 | fn jmp_insns(&self) -> Vec<(Vec, Vec)> { 38 | vec![] 39 | } 40 | 41 | fn insn_step(&self) -> usize { 42 | 4 43 | } 44 | } 45 | 46 | impl std::fmt::Debug for Arm { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | f.debug_struct("Arm").finish() 49 | } 50 | } 51 | 52 | pub struct Arm64; 53 | 54 | impl cpu::Cpu for Arm64 { 55 | fn cpu_type(&self) -> cpu::CpuType { 56 | cpu::CpuType::ARM64 57 | } 58 | 59 | fn ptrsize(&self) -> usize { 60 | 8 61 | } 62 | 63 | fn ret_insns(&self) -> Vec<(Vec, Vec)> { 64 | vec![ 65 | ( 66 | vec![0xd6, 0x5f, 0x03, 0xc0].into_iter().rev().collect(), 67 | vec![0xff, 0xff, 0xff, 0xff].into_iter().rev().collect(), 68 | ), // RET 69 | ] 70 | } 71 | 72 | fn call_insns(&self) -> Vec<(Vec, Vec)> { 73 | vec![ 74 | // (vec![0x14], vec![0xff]), // B LABEL 75 | // (vec![0x01, 0x14], vec![0xff, 0xff]), // BL LABEL 76 | // (vec![0xd4], vec![0xff]), // B.cond 77 | // (vec![0xb4], vec![0xff]), // CBZ // CBNZ 78 | ( 79 | vec![0b1101_0110, 0b0011_1111, 0b0000_0000, 0b0000_0000] 80 | .into_iter() 81 | .rev() 82 | .collect(), 83 | vec![0b1111_1111, 0b1111_1111, 0b1111_0000, 0b0001_1111] 84 | .into_iter() 85 | .rev() 86 | .collect(), 87 | ), // C6.2.35 BLR 88 | ] 89 | } 90 | 91 | fn jmp_insns(&self) -> Vec<(Vec, Vec)> { 92 | vec![ 93 | ( 94 | vec![0b1101_0110, 0b0001_1111, 0b0000_0000, 0b0000_0000] 95 | .into_iter() 96 | .rev() 97 | .collect(), 98 | vec![0b1111_1111, 0b1111_1111, 0b1111_0000, 0b0001_1111] 99 | .into_iter() 100 | .rev() 101 | .collect(), 102 | ), // C6.2.37 BR 103 | ] 104 | } 105 | 106 | fn insn_step(&self) -> usize { 107 | 4 108 | } 109 | } 110 | 111 | impl std::fmt::Debug for Arm64 { 112 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 113 | f.debug_struct("Arm64").finish() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arm; 2 | pub mod x86; 3 | 4 | use clap::ValueEnum; 5 | 6 | #[derive(std::fmt::Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 7 | #[derive(Default)] 8 | pub enum CpuType { 9 | #[default] 10 | Unknown, 11 | X86, 12 | X64, 13 | ARM, 14 | ARM64, 15 | } 16 | 17 | pub trait Cpu: Send + Sync + std::fmt::Debug { 18 | fn cpu_type(&self) -> CpuType; 19 | fn ptrsize(&self) -> usize; 20 | fn insn_step(&self) -> usize; 21 | 22 | // 23 | // for each instruction type, the format is Vector 24 | // 25 | 26 | fn ret_insns(&self) -> Vec<(Vec, Vec)>; 27 | fn call_insns(&self) -> Vec<(Vec, Vec)>; 28 | fn jmp_insns(&self) -> Vec<(Vec, Vec)>; 29 | 30 | fn name(&self) -> String { 31 | self.cpu_type().to_string() 32 | } 33 | } 34 | 35 | impl std::fmt::Display for CpuType { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | let val = match self { 38 | CpuType::X86 => "x86-32", 39 | CpuType::X64 => "x86-64", 40 | CpuType::ARM => "ARM", 41 | CpuType::ARM64 => "ARM64", 42 | CpuType::Unknown => "Unknown", 43 | }; 44 | 45 | write!(f, "Arch={}", val) 46 | } 47 | } 48 | 49 | 50 | 51 | impl From<&goblin::elf::header::Header> for CpuType { 52 | fn from(value: &goblin::elf::header::Header) -> Self { 53 | match value.e_machine { 54 | goblin::elf::header::EM_386 => CpuType::X86, 55 | goblin::elf::header::EM_X86_64 => CpuType::X64, 56 | goblin::elf::header::EM_ARM => CpuType::ARM, 57 | goblin::elf::header::EM_AARCH64 => CpuType::ARM64, 58 | _ => panic!("ELF machine format is unsupported"), 59 | } 60 | } 61 | } 62 | 63 | impl From<&goblin::mach::header::Header> for CpuType { 64 | fn from(value: &goblin::mach::header::Header) -> Self { 65 | match value.cputype { 66 | goblin::mach::constants::cputype::CPU_TYPE_X86 => CpuType::X86, 67 | goblin::mach::constants::cputype::CPU_TYPE_X86_64 => CpuType::X64, 68 | goblin::mach::constants::cputype::CPU_TYPE_ARM => CpuType::ARM, 69 | goblin::mach::constants::cputype::CPU_TYPE_ARM64 => CpuType::ARM64, 70 | _ => panic!("MachO is corrupted"), 71 | } 72 | } 73 | } 74 | 75 | impl From<&goblin::pe::header::CoffHeader> for CpuType { 76 | fn from(obj: &goblin::pe::header::CoffHeader) -> Self { 77 | match obj.machine { 78 | goblin::pe::header::COFF_MACHINE_X86 => CpuType::X86, 79 | goblin::pe::header::COFF_MACHINE_X86_64 => CpuType::X64, 80 | goblin::pe::header::COFF_MACHINE_ARM => CpuType::ARM, 81 | goblin::pe::header::COFF_MACHINE_ARMNT => CpuType::ARM, 82 | goblin::pe::header::COFF_MACHINE_ARM64 => CpuType::ARM64, 83 | _ => panic!("Unsupported format"), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/cpu/x86.rs: -------------------------------------------------------------------------------- 1 | use std::vec; 2 | 3 | use crate::cpu; 4 | 5 | pub struct X86; 6 | 7 | impl cpu::Cpu for X86 { 8 | fn cpu_type(&self) -> cpu::CpuType { 9 | cpu::CpuType::X86 10 | } 11 | 12 | fn ptrsize(&self) -> usize { 13 | 4 14 | } 15 | 16 | fn ret_insns(&self) -> Vec<(Vec, Vec)> { 17 | vec![ 18 | (vec![0xc3], vec![0xff]), // ret 19 | (vec![0xcb], vec![0xff]), // retf 20 | (vec![0xc2, 0x00, 0x00], vec![0xff, 0x00, 0x00]), // ret imm16 21 | (vec![0xcf, 0x00, 0x00], vec![0xff, 0x00, 0x00]), // retf imm16 22 | ] 23 | } 24 | 25 | fn call_insns(&self) -> Vec<(Vec, Vec)> { 26 | vec![ 27 | // ( 28 | // vec![0xe8, 0x00, 0x00, 0x00, 0x00], 29 | // vec![0xff, 0x00, 0x00, 0x00, 0x00], 30 | // ), // CALL rel32 31 | (vec![0xff, 0xd0], vec![0xff, 0xf0]), // CALL REG32 32 | (vec![0xff, 0b0001_0000], vec![0xff, 0b1111_0000]), // CALL [REG32] 33 | (vec![0xff, 0b0101_0001, 0], vec![0xff, 0b1111_0000, 0]), // CALL [REG32+DISP8] 34 | ] 35 | } 36 | 37 | fn jmp_insns(&self) -> Vec<(Vec, Vec)> { 38 | vec![ 39 | // (vec![0xe9, 0, 0, 0, 0], vec![0xff, 0, 0, 0, 0]), // JMP imm32 40 | (vec![0xFF, 0xe7], vec![0xff, 0xf8]), // JMP REG32 41 | ] 42 | } 43 | 44 | fn insn_step(&self) -> usize { 45 | 1 46 | } 47 | } 48 | 49 | impl std::fmt::Debug for X86 { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | f.debug_struct("X86").finish() 52 | } 53 | } 54 | 55 | pub struct X64; 56 | 57 | impl cpu::Cpu for X64 { 58 | fn cpu_type(&self) -> cpu::CpuType { 59 | cpu::CpuType::X64 60 | } 61 | 62 | fn ptrsize(&self) -> usize { 63 | 8 64 | } 65 | 66 | fn ret_insns(&self) -> Vec<(Vec, Vec)> { 67 | vec![ 68 | (vec![0xc3], vec![0xff]), // RET 69 | (vec![0xcb], vec![0xff]), // RETF 70 | (vec![0xc2, 0x00, 0x00], vec![0xff, 0x00, 0x00]), // RET imm16 71 | (vec![0xcf, 0x00, 0x00], vec![0xff, 0x00, 0x00]), // RETF imm16 72 | ] 73 | } 74 | 75 | fn call_insns(&self) -> Vec<(Vec, Vec)> { 76 | vec![ 77 | // ( 78 | // vec![0xe8, 0x00, 0x00, 0x00, 0x00], 79 | // vec![0xff, 0x00, 0x00, 0x00, 0x00], 80 | // ), // CALL rel32 81 | (vec![0xff, 0xd0], vec![0xff, 0xf0]), // CALL REG64 82 | (vec![0x41, 0xff, 0xd0], vec![0xff, 0xff, 0xf0]), // CALL REX.W REG64 83 | (vec![0xff, 0b0001_0000], vec![0xff, 0b1111_0000]), // CALL [REG64] 84 | (vec![0x41, 0xff, 0b0001_0000], vec![0x41, 0xff, 0b1111_0000]), // CALL [REX.W REG64] 85 | (vec![0xff, 0b0101_0001, 0], vec![0xff, 0b1111_0000, 0]), // CALL [REG64+DISP8] 86 | ( 87 | vec![0x41, 0xff, 0b0101_0001, 0], 88 | vec![0xff, 0xff, 0b1111_0000, 0], 89 | ), // CALL [REX.W REG64+DISP8] 90 | ] 91 | } 92 | 93 | fn jmp_insns(&self) -> Vec<(Vec, Vec)> { 94 | vec![ 95 | (vec![0xff, 0xe0], vec![0xff, 0xf8]), // JMP REG64 96 | (vec![0x41, 0xff, 0xe0], vec![0xff, 0xff, 0xf8]), // JMP REX.W REG64 97 | // (vec![0xeb, 0x00], vec![0xff, 0x00]), // JMP imm8 98 | // (vec![0xe9, 0, 0, 0, 0], vec![0xff, 0, 0, 0, 0]), // JMP imm32 99 | ] 100 | } 101 | 102 | fn insn_step(&self) -> usize { 103 | 1 104 | } 105 | } 106 | 107 | impl std::fmt::Debug for X64 { 108 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 109 | f.debug_struct("X64").finish() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use capstone::prelude::*; 2 | 3 | use crate::cpu::{Cpu, CpuType}; 4 | use crate::gadget::{Instruction, InstructionGroup}; 5 | 6 | /** 7 | * 8 | * for now we just use capstone, but can be easily updated to other engines 9 | * in this case, change engine.rs -> engine/mod.rs and put the engines there 10 | * 11 | */ 12 | 13 | #[derive(Debug, Default)] 14 | pub enum DisassemblyEngineType { 15 | #[default] 16 | Invalid, 17 | Capstone, 18 | } 19 | 20 | // impl Default for DisassemblyEngineType { 21 | // fn default() -> Self { 22 | // DisassemblyEngineType::Invalid 23 | // } 24 | // } 25 | 26 | // 27 | // All disassembler must implement this trait 28 | // 29 | pub trait Disassembler { 30 | fn disassemble(&self, code: &Vec, address: u64) -> Option>; 31 | fn name(&self) -> String; 32 | fn id(&self) -> DisassemblyEngineType; 33 | } 34 | 35 | pub struct DisassemblyEngine { 36 | pub disassembler: Box, 37 | } 38 | 39 | impl DisassemblyEngine { 40 | /// 41 | /// 42 | /// 43 | pub fn new(engine_type: &DisassemblyEngineType, cpu: &dyn Cpu) -> Self { 44 | match engine_type { 45 | DisassemblyEngineType::Capstone => Self { 46 | disassembler: Box::new(CapstoneDisassembler::new(cpu)), 47 | }, 48 | DisassemblyEngineType::Invalid => panic!(), 49 | } 50 | } 51 | } 52 | 53 | impl std::fmt::Display for DisassemblyEngine { 54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | write!(f, "Engine({})", self.disassembler.name()) 56 | } 57 | } 58 | 59 | /// 60 | /// Capstone disassembler implementation 61 | /// 62 | 63 | // https://github.com/aquynh/capstone/blob/1b5014515d0d671048e2b43ce483d38d85a2bc83/bindings/python/capstone/__init__.py#L216 64 | const INSN_GRP_JUMP: u8 = 0x01; 65 | const INSN_GRP_CALL: u8 = 0x02; 66 | const INSN_GRP_RET: u8 = 0x03; 67 | const INSN_GRP_INT: u8 = 0x04; 68 | const INSN_GRP_IRET: u8 = 0x05; 69 | const INSN_GRP_PRIV: u8 = 0x06; 70 | 71 | pub struct CapstoneDisassembler { 72 | cs: Capstone, 73 | } 74 | 75 | impl Disassembler for CapstoneDisassembler { 76 | fn disassemble(&self, code: &Vec, address: u64) -> Option> { 77 | self.cs_disassemble(code, address) 78 | } 79 | 80 | fn name(&self) -> String { 81 | // todo: add version strings 82 | let (major, minor) = Capstone::lib_version(); 83 | format!("Capstone-Engine({}.{})", major, minor) 84 | } 85 | 86 | fn id(&self) -> DisassemblyEngineType { 87 | DisassemblyEngineType::Capstone 88 | } 89 | } 90 | 91 | impl std::fmt::Display for CapstoneDisassembler { 92 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 93 | write!(f, "Disassembler({})", self.name()) 94 | } 95 | } 96 | 97 | impl CapstoneDisassembler { 98 | fn new(cpu: &dyn Cpu) -> Self { 99 | let cs = match cpu.cpu_type() { 100 | CpuType::X86 => Capstone::new() 101 | .x86() 102 | .mode(arch::x86::ArchMode::Mode32) 103 | .syntax(arch::x86::ArchSyntax::Intel) 104 | .detail(true) 105 | .build() 106 | .expect("Failed to create Capstone object"), 107 | 108 | CpuType::X64 => Capstone::new() 109 | .x86() 110 | .mode(arch::x86::ArchMode::Mode64) 111 | .syntax(arch::x86::ArchSyntax::Intel) 112 | .detail(true) 113 | .build() 114 | .expect("Failed to create Capstone object"), 115 | 116 | CpuType::ARM => Capstone::new() 117 | .arm() 118 | .mode(arch::arm::ArchMode::Arm) 119 | .detail(true) 120 | .build() 121 | .expect("Failed to create Capstone object"), 122 | 123 | CpuType::ARM64 => Capstone::new() 124 | .arm64() 125 | .mode(arch::arm64::ArchMode::Arm) 126 | .detail(true) 127 | .build() 128 | .expect("Failed to create Capstone object"), 129 | 130 | CpuType::Unknown => panic!(), 131 | }; 132 | 133 | Self { cs } 134 | } 135 | 136 | fn cs_disassemble(&self, code: &Vec, address: u64) -> Option> { 137 | let cs_insns = self 138 | .cs 139 | .disasm_all(code, address) 140 | .expect("Failed to disassemble"); 141 | 142 | // 143 | // Any instruction? 144 | // 145 | if cs_insns.len() == 0 { 146 | return None; 147 | } 148 | 149 | // 150 | // Otherwise we're good to proceed 151 | // 152 | let mut insns: Vec = Vec::new(); 153 | let mut candidates: Vec = Vec::new(); 154 | 155 | for cs_insn in cs_insns.iter() { 156 | let detail: InsnDetail = self.cs.insn_detail(cs_insn).unwrap(); 157 | 158 | let mut insn_group = InstructionGroup::Undefined; 159 | 160 | for cs_insn_group in detail.groups() { 161 | insn_group = match cs_insn_group.0 { 162 | INSN_GRP_JUMP => InstructionGroup::Jump, 163 | INSN_GRP_CALL => InstructionGroup::Call, 164 | INSN_GRP_RET => InstructionGroup::Ret, 165 | INSN_GRP_PRIV => InstructionGroup::Privileged, 166 | INSN_GRP_INT => InstructionGroup::Int, 167 | INSN_GRP_IRET => InstructionGroup::Iret, 168 | _ => { 169 | continue; 170 | } 171 | }; 172 | } 173 | 174 | let mnemonic = cs_insn.mnemonic().unwrap().to_string(); 175 | 176 | let operands: Option = cs_insn.op_str().map(|op| op.to_string()); 177 | 178 | let insn = Instruction { 179 | raw: cs_insn.bytes().to_vec(), 180 | size: cs_insn.bytes().len(), 181 | mnemonic, 182 | operands, 183 | address: cs_insn.address(), 184 | group: insn_group, 185 | }; 186 | 187 | candidates.push(insn); 188 | } 189 | 190 | // 191 | // at this point `candidates` holds a valid set of Instruction 192 | // must filter out the sequence that can't qualify for a rop sequence 193 | // 194 | for insn in candidates.into_iter().rev() { 195 | match insn.group { 196 | InstructionGroup::Jump => { 197 | if !insns.is_empty() { 198 | break; 199 | } 200 | } 201 | InstructionGroup::Call => { 202 | if !insns.is_empty() { 203 | break; 204 | } 205 | } 206 | InstructionGroup::Ret => { 207 | if !insns.is_empty() { 208 | break; 209 | } 210 | } 211 | _ => {} 212 | }; 213 | 214 | insns.insert(0, insn); 215 | } 216 | 217 | Some(insns) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | IoError(std::io::Error), 4 | ParsingError(goblin::error::Error), 5 | ThreadRuntimeError(std::boxed::Box), 6 | InvalidFileError, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub struct GenericError { 11 | msg: String, 12 | } 13 | 14 | impl std::fmt::Display for GenericError { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | write!(f, "{}'", self.msg) 17 | } 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct GadgetBuildError {} 22 | 23 | impl From for Error { 24 | fn from(error: std::io::Error) -> Self { 25 | Error::IoError(error) 26 | } 27 | } 28 | 29 | impl From for Error { 30 | fn from(error: goblin::error::Error) -> Self { 31 | Error::ParsingError(error) 32 | } 33 | } 34 | 35 | impl From> for Error { 36 | fn from(error: std::boxed::Box) -> Self { 37 | Error::ThreadRuntimeError(error) 38 | } 39 | } 40 | 41 | #[derive(Debug)] 42 | pub struct InvalidFormatError { 43 | format: String, 44 | } 45 | 46 | impl std::fmt::Display for InvalidFormatError { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | write!(f, "Unknown/unsupported format: {}'", self.format) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/format/elf.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | use goblin; 3 | use log::debug; 4 | use std::fs::File; 5 | use std::io::{BufReader, Read, Seek, SeekFrom}; 6 | use std::path::PathBuf; 7 | 8 | use crate::cpu; 9 | use crate::section::Permission; 10 | use crate::{format::FileFormat, section::Section}; 11 | 12 | use super::ExecutableFileFormat; 13 | 14 | #[derive(Debug)] 15 | pub struct Elf { 16 | path: PathBuf, 17 | sections: Vec
, 18 | // cpu: Box, 19 | cpu_type: cpu::CpuType, 20 | entry_point: u64, 21 | } 22 | 23 | impl Elf { 24 | pub fn new(path: PathBuf, obj: goblin::elf::Elf) -> Self { 25 | let filepath = path.to_str().unwrap(); 26 | 27 | let mut executable_sections: Vec
= Vec::new(); 28 | debug!( 29 | "looking for executable sections in ELF: '{}'", 30 | filepath.bold() 31 | ); 32 | 33 | let file = File::open(&path).unwrap(); 34 | let mut reader = BufReader::new(file); 35 | 36 | for current_section in &obj.section_headers { 37 | // trace!("Testing section {:?}", s); 38 | 39 | // // 40 | // // disregard non executable section 41 | // // 42 | // if !s.is_executable() { 43 | // continue; 44 | // } 45 | 46 | // debug!("Importing section {:?}", s); 47 | 48 | // let mut section = Section::from(s); 49 | // section.name = Some(String::from(&obj.shdr_strtab[s.sh_name])); 50 | 51 | let mut sect = 52 | Section::from(current_section).name(&obj.shdr_strtab[current_section.sh_name]); 53 | 54 | if !sect.permission.contains(Permission::EXECUTABLE) { 55 | continue; 56 | } 57 | 58 | if reader 59 | .seek(SeekFrom::Start(current_section.sh_addr)) 60 | .is_err() 61 | { 62 | panic!("Invalid offset {}", current_section.sh_addr,) 63 | } 64 | 65 | match reader.read_exact(&mut sect.data) { 66 | Ok(_) => {} 67 | Err(e) => panic!( 68 | "Failed to extract section '{}' (size={:#x}) at offset {:#x}: {:?}", 69 | §.name.clone().unwrap_or_default(), 70 | §.size(), 71 | sect.start_address, 72 | e 73 | ), 74 | }; 75 | 76 | debug!("Adding {}", sect); 77 | executable_sections.push(sect); 78 | } 79 | 80 | // let cpu_type = match obj.header.e_machine { 81 | // goblin::elf::header::EM_386 => cpu::CpuType::X86, 82 | // goblin::elf::header::EM_X86_64 => cpu::CpuType::X64, 83 | // goblin::elf::header::EM_ARM => cpu::CpuType::ARM, 84 | // goblin::elf::header::EM_AARCH64 => cpu::CpuType::ARM64, 85 | // _ => { 86 | // panic!("ELF machine format is unsupported") 87 | // } 88 | // }; 89 | 90 | Self { 91 | path: path.clone(), 92 | sections: executable_sections, 93 | cpu_type: cpu::CpuType::from(&obj.header), 94 | entry_point: obj.entry, 95 | } 96 | } 97 | } 98 | 99 | impl ExecutableFileFormat for Elf { 100 | fn path(&self) -> &PathBuf { 101 | &self.path 102 | } 103 | 104 | fn format(&self) -> FileFormat { 105 | FileFormat::Elf 106 | } 107 | 108 | fn sections(&self) -> &Vec
{ 109 | &self.sections 110 | } 111 | 112 | // fn cpu(&self) -> &dyn cpu::Cpu { 113 | // self.cpu.as_ref() 114 | // } 115 | 116 | fn cpu_type(&self) -> cpu::CpuType { 117 | self.cpu_type 118 | } 119 | 120 | fn entry_point(&self) -> u64 { 121 | self.entry_point 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/format/mach.rs: -------------------------------------------------------------------------------- 1 | // use std::fs::File; 2 | // use std::io::{BufReader, Read, Seek, SeekFrom}; 3 | use std::path::PathBuf; 4 | 5 | use colored::Colorize; 6 | use goblin; 7 | use log::debug; 8 | 9 | use crate::cpu; 10 | use crate::{format::FileFormat, section::Permission, section::Section}; 11 | 12 | use super::ExecutableFileFormat; 13 | 14 | pub struct Mach { 15 | path: PathBuf, 16 | sections: Vec
, 17 | cpu_type: cpu::CpuType, 18 | entry_point: u64, 19 | } 20 | impl Mach { 21 | pub fn new(path: PathBuf, obj: goblin::mach::Mach) -> Self { 22 | let bin = match obj { 23 | goblin::mach::Mach::Binary(macho) => macho, 24 | goblin::mach::Mach::Fat(_) => todo!(), 25 | }; 26 | 27 | let filepath = path.to_str().unwrap(); 28 | 29 | let mut executable_sections: Vec
= Vec::new(); 30 | 31 | debug!( 32 | "looking for executables sections in MachO: '{}'", 33 | filepath.bold() 34 | ); 35 | 36 | for current_segment in &bin.segments { 37 | // for current_section in current_segment.sections().iter() { 38 | // if s.flags & constants::S_ATTR_PURE_INSTRUCTIONS == 0 39 | // || s.flags & constants::S_ATTR_SOME_INSTRUCTIONS == 0 40 | // { 41 | // continue; 42 | // } 43 | 44 | // let section_name = match std::str::from_utf8(&s.segname) { 45 | // Ok(v) => String::from(v).replace("\0", ""), 46 | // Err(_) => "".to_string(), 47 | // }; 48 | 49 | // let mut section = Section::new(s.vmaddr as u64, (s.vmaddr + s.vmsize - 1) as u64); 50 | 51 | // section.name = Some(section_name); 52 | 53 | // let perm = Permission::EXECUTABLE | Permission::READABLE; // todo: fix later 54 | // section.permission = perm; 55 | 56 | let section = Section::from(current_segment).data(current_segment.data.to_vec()); 57 | 58 | if !section.permission.contains(Permission::EXECUTABLE) { 59 | continue; 60 | } 61 | 62 | // reader 63 | // .seek(SeekFrom::Start(current_segment.fileoff as u64)) 64 | // .unwrap(); 65 | // reader.read_exact(&mut section.data).unwrap(); 66 | 67 | debug!("Adding {}", section); 68 | executable_sections.push(section); 69 | // } 70 | } 71 | 72 | // let cpu_type = match bin.header.cputype { 73 | // constants::cputype::CPU_TYPE_X86 => cpu::CpuType::X86, 74 | // constants::cputype::CPU_TYPE_X86_64 => cpu::CpuType::X64, 75 | // constants::cputype::CPU_TYPE_ARM => cpu::CpuType::ARM, 76 | // constants::cputype::CPU_TYPE_ARM64 => cpu::CpuType::ARM64, 77 | // _ => { 78 | // panic!("MachO is corrupted") 79 | // } 80 | // }; 81 | 82 | Self { 83 | path: path.clone(), 84 | sections: executable_sections, 85 | cpu_type: cpu::CpuType::from(&bin.header), 86 | entry_point: bin.entry, 87 | } 88 | } 89 | } 90 | 91 | impl ExecutableFileFormat for Mach { 92 | fn path(&self) -> &PathBuf { 93 | &self.path 94 | } 95 | 96 | fn format(&self) -> FileFormat { 97 | FileFormat::MachO 98 | } 99 | 100 | fn sections(&self) -> &Vec
{ 101 | &self.sections 102 | } 103 | 104 | // fn cpu(&self) -> &dyn cpu::Cpu { 105 | // self.cpu.as_ref() 106 | // } 107 | 108 | fn cpu_type(&self) -> cpu::CpuType { 109 | self.cpu_type 110 | } 111 | 112 | fn entry_point(&self) -> u64 { 113 | self.entry_point 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod elf; 2 | pub mod mach; 3 | pub mod pe; 4 | 5 | use std::{fs, path::PathBuf}; 6 | 7 | use crate::{common::GenericResult, cpu::CpuType, error::Error, section::Section}; 8 | 9 | use clap::ValueEnum; 10 | use goblin::Object; 11 | 12 | #[derive(std::fmt::Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)] 13 | pub enum FileFormat { 14 | #[default] 15 | Auto, 16 | Pe, 17 | Elf, 18 | MachO, 19 | // todo: Raw, 20 | } 21 | 22 | impl std::fmt::Display for FileFormat { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | let val = match self { 25 | FileFormat::Pe => "PE", 26 | FileFormat::Elf => "ELF", 27 | FileFormat::MachO => "MachO", 28 | _ => panic!("Invalid FileFormat"), 29 | }; 30 | 31 | write!(f, "BinaryFormat={}", val) 32 | } 33 | } 34 | 35 | /// Trait specific to executable files 36 | pub trait ExecutableFileFormat: Send + Sync { 37 | fn path(&self) -> &PathBuf; 38 | 39 | fn format(&self) -> FileFormat; 40 | 41 | fn sections(&self) -> &Vec
; 42 | 43 | // fn cpu(&self) -> &dyn cpu::Cpu; 44 | 45 | fn cpu_type(&self) -> CpuType; 46 | 47 | fn entry_point(&self) -> u64; 48 | } 49 | 50 | /// Attempt to determine the file 51 | pub fn guess_file_format(file: &PathBuf) -> GenericResult> { 52 | if !file.as_path().exists() { 53 | return Err(Error::InvalidFileError); 54 | } 55 | 56 | let buffer = match fs::read(file.as_path()) { 57 | Ok(buf) => buf, 58 | Err(_) => return Err(Error::InvalidFileError), 59 | }; 60 | 61 | let parsed = match Object::parse(&buffer) { 62 | Ok(e) => e, 63 | Err(_) => return Err(Error::InvalidFileError), 64 | }; 65 | 66 | match parsed { 67 | Object::PE(obj) => Ok(Box::new(pe::Pe::new(file.to_path_buf(), obj))), 68 | Object::Elf(obj) => Ok(Box::new(elf::Elf::new(file.to_path_buf(), obj))), 69 | Object::Mach(obj) => Ok(Box::new(mach::Mach::new(file.to_path_buf(), obj))), 70 | Object::Archive(_) => Err(Error::InvalidFileError), 71 | Object::Unknown(_) => Err(Error::InvalidFileError), 72 | _ => Err(Error::InvalidFileError), 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/format/pe.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, Read, Seek, SeekFrom}; 3 | use std::path::PathBuf; 4 | 5 | use goblin; 6 | use log::debug; 7 | 8 | use crate::cpu::{self, CpuType}; 9 | // use crate::cpu; 10 | use crate::{format::FileFormat, section::Permission, section::Section}; 11 | 12 | use super::ExecutableFileFormat; 13 | 14 | #[derive(Debug)] 15 | #[derive(Default)] 16 | pub struct Pe { 17 | path: PathBuf, 18 | pub sections: Vec
, 19 | // cpu: Box, 20 | pub entry_point: u64, 21 | 22 | cpu_type: cpu::CpuType, 23 | } 24 | 25 | 26 | 27 | impl Pe { 28 | pub fn new(path: PathBuf, obj: goblin::pe::PE<'_>) -> Self { 29 | let mut executable_sections: Vec
= Vec::new(); 30 | let file = File::open(&path).unwrap(); 31 | let mut reader = BufReader::new(file); 32 | 33 | for current_section in &obj.sections { 34 | // if s.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_EXECUTE == 0 { 35 | // continue; 36 | // } 37 | 38 | // let section_name = match std::str::from_utf8(&s.name) { 39 | // Ok(v) => String::from(v).replace("\0", ""), 40 | // Err(_) => String::new(), 41 | // }; 42 | 43 | // let mut section = Section::new( 44 | // s.virtual_address as u64, 45 | // (s.virtual_address + s.virtual_size - 1) as u64, 46 | // ); 47 | 48 | // section.name = Some(section_name); 49 | 50 | // let mut perm = Permission::EXECUTABLE; 51 | // if s.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_READ != 0 { 52 | // perm |= Permission::READABLE; 53 | // } 54 | 55 | // if s.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_WRITE != 0 { 56 | // perm |= Permission::WRITABLE; 57 | // } 58 | 59 | // section.permission = perm; 60 | 61 | // let data = s.data(); 62 | 63 | let mut section = Section::from(current_section); 64 | 65 | if !section.permission.contains(Permission::EXECUTABLE) { 66 | continue; 67 | } 68 | 69 | match reader.seek(SeekFrom::Start(current_section.pointer_to_raw_data as u64)) { 70 | Ok(_) => {} 71 | Err(e) => panic!("Corrupted PE: {:?}", e), 72 | }; 73 | 74 | reader.read_exact(&mut section.data).unwrap(); 75 | 76 | debug!("Adding {}", section); 77 | executable_sections.push(section); 78 | } 79 | 80 | Self { 81 | path: path.clone(), 82 | sections: executable_sections, 83 | // cpu, 84 | cpu_type: CpuType::from(&obj.header.coff_header), 85 | entry_point: obj.entry as u64, 86 | ..Default::default() 87 | } 88 | } 89 | } 90 | 91 | impl ExecutableFileFormat for Pe { 92 | fn path(&self) -> &PathBuf { 93 | &self.path 94 | } 95 | 96 | fn format(&self) -> FileFormat { 97 | FileFormat::Pe 98 | } 99 | 100 | fn sections(&self) -> &Vec
{ 101 | &self.sections 102 | } 103 | 104 | // fn cpu(&self) -> &dyn cpu::Cpu { 105 | // self.cpu.as_ref() 106 | // } 107 | 108 | fn entry_point(&self) -> u64 { 109 | self.entry_point 110 | } 111 | 112 | fn cpu_type(&self) -> cpu::CpuType { 113 | self.cpu_type 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/gadget.rs: -------------------------------------------------------------------------------- 1 | extern crate capstone; 2 | 3 | use std::cmp::Ordering; 4 | use std::{fmt, thread}; 5 | use std::{ 6 | io::{Cursor, Read, Seek, SeekFrom}, 7 | sync::Arc, 8 | }; 9 | 10 | use colored::*; 11 | use log::{debug, warn}; 12 | 13 | use crate::common::GenericResult; 14 | use crate::cpu; 15 | use crate::engine::Disassembler; 16 | use crate::section::Section; 17 | use crate::session::{RopProfileStrategy, Session}; 18 | 19 | use clap::ValueEnum; 20 | 21 | #[derive(std::fmt::Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 22 | pub enum InstructionGroup { 23 | Undefined, 24 | Jump, 25 | Call, 26 | Ret, 27 | Int, 28 | Iret, 29 | Privileged, 30 | } 31 | 32 | impl std::fmt::Display for InstructionGroup { 33 | fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { 34 | std::fmt::Debug::fmt(self, f) 35 | } 36 | } 37 | 38 | #[derive(Debug, PartialEq, Clone)] 39 | pub struct Instruction { 40 | pub size: usize, 41 | pub raw: Vec, 42 | pub address: u64, 43 | pub group: InstructionGroup, 44 | 45 | pub mnemonic: String, 46 | pub operands: Option, 47 | } 48 | 49 | impl Instruction { 50 | pub fn text(&self, use_color: bool) -> String { 51 | let mnemo = match use_color { 52 | true => { 53 | format!("{}", self.mnemonic.cyan()) 54 | } 55 | false => { 56 | self.mnemonic.to_string() 57 | } 58 | }; 59 | 60 | let op = match &self.operands { 61 | Some(x) => { 62 | if use_color { 63 | format!(" {}", x.bold()) 64 | } else { 65 | format!(" {}", x) 66 | } 67 | } 68 | None => "".to_string(), 69 | }; 70 | 71 | format!("{}{}", mnemo, op) 72 | } 73 | } 74 | 75 | impl fmt::Display for Instruction { 76 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 | write!( 78 | f, 79 | "Instruction(addr=0x{:x}, size={}, text='{}', raw={:?}, group={:?})", 80 | self.address, 81 | self.size, 82 | self.text(false), 83 | self.raw, 84 | self.group 85 | ) 86 | } 87 | } 88 | 89 | #[derive(Debug, PartialEq, Clone)] 90 | pub struct Gadget { 91 | pub address: u64, 92 | pub insns: Vec, 93 | pub size: usize, // sum() of sizeof(each_instruction) 94 | pub raw: Vec, // concat() of instruction.raw 95 | } 96 | 97 | impl fmt::Display for Gadget { 98 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 | write!( 100 | f, 101 | "Gadget(addr={:#x}, text='{}')", 102 | self.address, 103 | self.text(false) 104 | ) 105 | } 106 | } 107 | 108 | impl Gadget { 109 | pub fn new(insns: Vec) -> Self { 110 | // 111 | // by nature, we should never be here if insns.len() is 0 (should at least have the 112 | // ret insn) so we assert() to be notified 113 | // 114 | if insns.is_empty() { 115 | std::panic::panic_any("GadgetBuildError"); 116 | } 117 | 118 | let size = insns.iter().map(|x| x.size).sum(); 119 | 120 | let raw = insns.iter().flat_map(|x| x.raw.clone()).collect(); 121 | 122 | let address = insns.first().unwrap().address; 123 | 124 | Self { 125 | size, 126 | raw, 127 | address, 128 | insns, 129 | } 130 | } 131 | 132 | pub fn text(&self, use_color: bool) -> String { 133 | self.insns 134 | .iter() 135 | .map(|i| i.text(use_color).clone() + " ; ") 136 | .collect() 137 | } 138 | } 139 | 140 | fn collect_previous_instructions( 141 | session: &Arc, 142 | group: &Vec<(Vec, Vec)>, 143 | memory_chunk: &Vec, 144 | ) -> GenericResult> { 145 | let mut res: Vec<(usize, usize)> = Vec::new(); 146 | 147 | for (opcodes, mask) in group { 148 | let sz = opcodes.len(); 149 | 150 | let mut v: Vec<(usize, usize)> = memory_chunk 151 | .windows(sz) 152 | .enumerate() 153 | .filter(|(_, y)| { 154 | y.iter() 155 | .enumerate() 156 | .map(|(i, z)| z & mask[i]) 157 | .cmp(opcodes.to_owned()) 158 | == Ordering::Equal 159 | }) 160 | .map(|(x, _)| (x, sz)) 161 | .collect(); 162 | 163 | res.append(&mut v); 164 | 165 | match session.profile_type { 166 | RopProfileStrategy::Fast => { 167 | break; 168 | } 169 | _ => {} 170 | } 171 | } 172 | 173 | Ok(res) 174 | } 175 | 176 | pub fn get_all_valid_positions_and_length( 177 | session: &Arc, 178 | cpu: &Box, 179 | section: &Section, 180 | cursor: usize, 181 | ) -> GenericResult> { 182 | let data = §ion.data[cursor..].to_vec(); 183 | 184 | let mut groups = Vec::new(); 185 | 186 | for gadget_type in &session.gadget_types { 187 | match gadget_type { 188 | InstructionGroup::Ret => { 189 | debug!("inserting ret positions and length..."); 190 | groups.append(&mut cpu.ret_insns().clone()); 191 | } 192 | InstructionGroup::Call => { 193 | debug!("inserting call positions and length..."); 194 | groups.append(&mut cpu.call_insns().clone()); 195 | } 196 | InstructionGroup::Jump => { 197 | debug!("inserting jump positions and length..."); 198 | groups.append(&mut cpu.jmp_insns().clone()); 199 | } 200 | InstructionGroup::Int => todo!(), 201 | InstructionGroup::Iret => todo!(), 202 | InstructionGroup::Privileged => todo!(), 203 | InstructionGroup::Undefined => todo!(), 204 | } 205 | } 206 | 207 | collect_previous_instructions(session, &groups, data) 208 | } 209 | 210 | /// 211 | /// from the section.data[pos], disassemble previous instructions 212 | /// 213 | pub fn find_gadgets_from_position( 214 | session: Arc, 215 | engine: &dyn Disassembler, 216 | section: &Section, 217 | initial_position: usize, 218 | initial_len: usize, 219 | cpu: &Box, 220 | ) -> GenericResult> { 221 | let max_invalid_size = match cpu.cpu_type() // todo: use session.max_gadget_length 222 | { 223 | cpu::CpuType::X86 => { 16 } 224 | cpu::CpuType::X64 => { 16 } 225 | cpu::CpuType::ARM64 => { 16 } 226 | cpu::CpuType::ARM => { 16 } 227 | cpu::CpuType::Unknown => panic!(), 228 | }; 229 | 230 | let start_address = section.start_address; 231 | let s: usize = if initial_position < max_invalid_size { 232 | 0 233 | } else { 234 | initial_position - max_invalid_size 235 | }; 236 | let data = §ion.data[s..initial_position + initial_len]; 237 | let mut cur = Cursor::new(data); 238 | 239 | // 240 | // browse the section for the largest gadget 241 | // 242 | 243 | let mut sz: usize = initial_len; 244 | let mut nb_invalid = 0; 245 | let step = cpu.insn_step(); 246 | let mut gadgets: Vec = Vec::new(); 247 | 248 | loop { 249 | let mut candidate: Vec = vec![0; sz]; 250 | 251 | // 252 | // ensure we're still within the boundaries of the cursor 253 | // 254 | if (sz - step) >= data.len() { 255 | break; 256 | } 257 | 258 | // 259 | // jump to the position in the file 260 | // 261 | let current_position = -((sz - step) as i64); 262 | 263 | cur.seek(SeekFrom::End(current_position - step as i64))?; 264 | if cur.read_exact(&mut candidate).is_err() { 265 | warn!("{:?} Cursor reached EOF", std::thread::current().id()); 266 | break; 267 | } 268 | 269 | // 270 | // disassemble the code from given position 271 | // 272 | let addr = start_address + s as u64 + cur.position() - sz as u64; 273 | let insns = engine.disassemble(&candidate, addr as u64); 274 | 275 | // 276 | // transform the Vec into a valid gadget 277 | // 278 | match insns { 279 | Some(x) => { 280 | nb_invalid = 0; 281 | if !x.is_empty() { 282 | let last_insn = x.last().unwrap(); 283 | if session.gadget_types.contains(&last_insn.group) { 284 | let gadget = Gadget::new(x); 285 | if gadgets.iter().all(|x| x.address != gadget.address) { 286 | debug!( 287 | "{:?}: pushing new gadget(address={:x}, sz={})", 288 | thread::current().id(), 289 | gadget.address, 290 | gadget.raw.len() 291 | ); 292 | gadgets.push(gadget); 293 | } 294 | } 295 | } 296 | } 297 | 298 | None => { 299 | nb_invalid += 1; 300 | if nb_invalid == max_invalid_size { 301 | break; 302 | } 303 | } 304 | } 305 | 306 | sz += step; 307 | } 308 | 309 | Ok(gadgets) 310 | } 311 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bitflags; 3 | 4 | use colored::Colorize; 5 | use gadget::Gadget; 6 | use std::fs; 7 | use std::io::Write as _; 8 | use std::sync::Arc; 9 | 10 | use log::{debug, error, info, warn}; 11 | 12 | pub mod common; 13 | pub mod cpu; 14 | pub mod engine; 15 | pub mod error; 16 | pub mod format; 17 | pub mod gadget; 18 | pub mod section; 19 | pub mod session; 20 | 21 | use crate::common::GenericResult; 22 | use crate::session::Session; 23 | 24 | pub fn collect_all_gadgets(sess: Session) -> GenericResult> { 25 | let info = &sess.info; 26 | let start_timestamp = std::time::Instant::now(); 27 | let sections = info.format.sections(); 28 | 29 | let use_color = sess.use_color; 30 | let unique_only = sess.unique_only; 31 | let chosen_output_format = sess.output.clone(); 32 | let entrypoint_address = info.format.entry_point(); 33 | let is_64b = info.is_64b(); 34 | 35 | info!( 36 | "Looking for gadgets in {} executable section(s) (with {} threads)...'", 37 | sections.len(), 38 | sess.nb_thread, 39 | ); 40 | 41 | // 42 | // use an arc for the session to share between threads 43 | // 44 | let arc = Arc::new(sess); 45 | match session::find_gadgets(arc.clone()) { 46 | Ok(_) => { 47 | dbg!("Done collecting gadgets"); 48 | } 49 | Err(e) => { 50 | error!("An error occured while collecting gadgets: {:?}", e); 51 | return Err(e); 52 | } 53 | } 54 | 55 | let mut gadgets = arc.gadgets.lock().unwrap(); 56 | 57 | // 58 | // if unique, filter out doublons 59 | // 60 | let total_gadgets_found: usize = gadgets.len(); 61 | if unique_only { 62 | debug!( 63 | "Filtering {} gadgets for deplicates ...", 64 | total_gadgets_found 65 | ); 66 | gadgets.sort_by_key(|a| a.text(false)); 67 | gadgets.dedup_by(|a, b| a.text(false).eq_ignore_ascii_case(b.text(false).as_str())); 68 | info!( 69 | "{} duplicate gadgets removed", 70 | total_gadgets_found - gadgets.len() 71 | ); 72 | } 73 | 74 | // 75 | // sort by address 76 | // 77 | gadgets.sort_by(|a, b| a.address.cmp(&b.address)); 78 | 79 | // 80 | // Write to given output 81 | // 82 | match chosen_output_format { 83 | session::RopGadgetOutput::None => { 84 | warn!("No output specified"); 85 | } 86 | 87 | session::RopGadgetOutput::Console => { 88 | info!("Dumping {} gadgets to stdout...", gadgets.len()); 89 | 90 | for g in &*gadgets { 91 | let addr = match is_64b { 92 | true => { 93 | format!("0x{:016x}", g.address) 94 | } 95 | _ => { 96 | format!("0x{:08x}", g.address) 97 | } 98 | }; 99 | 100 | if use_color { 101 | println!("{} | {}", addr.red(), g.text(use_color)); 102 | } else { 103 | println!("{} | {}", addr, g.text(use_color)); 104 | } 105 | } 106 | } 107 | 108 | session::RopGadgetOutput::File(filename) => { 109 | dbg!( 110 | "Dumping {} gadgets to '{}'...", 111 | gadgets.len(), 112 | filename.to_str().unwrap() 113 | ); 114 | 115 | if use_color { 116 | warn!("Disabling colors when writing to file"); 117 | } 118 | 119 | let mut file = fs::File::create(&filename)?; 120 | for gadget in &*gadgets { 121 | let addr = entrypoint_address + gadget.address; 122 | file.write_all((format!("{:#x} | {}\n", addr, gadget.text(false))).as_bytes())?; 123 | } 124 | 125 | info!( 126 | "Written {} gadgets to '{}'", 127 | gadgets.len(), 128 | filename.to_str().unwrap() 129 | ); 130 | } 131 | } 132 | 133 | info!("Done!"); 134 | 135 | if log::log_enabled!(log::Level::Info) { 136 | let end_timestamp = std::time::Instant::now(); 137 | let elapsed = end_timestamp - start_timestamp; 138 | let execution_time = start_timestamp.elapsed().as_secs_f64(); 139 | info!("Execution time => {:?}", execution_time); 140 | info!( 141 | "Execution: {} gadgets found in {:?}", 142 | total_gadgets_found, elapsed 143 | ); 144 | } 145 | 146 | Ok(gadgets.clone()) 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use crate::{collect_all_gadgets, gadget::Gadget, session::RopGadgetOutput, Session}; 152 | use std::path::PathBuf; 153 | 154 | fn run_basic_test(sz: &str, arch: &str, fmt: &str) -> Vec { 155 | let input_fname = PathBuf::from(format!("tests/bin/{}-{}.{}", sz, arch, fmt)); 156 | let s = Session::new(input_fname).output(RopGadgetOutput::None); 157 | match collect_all_gadgets(s) { 158 | Ok(gadgets) => gadgets, 159 | Err(e) => panic!("{:?}", e), 160 | } 161 | } 162 | 163 | mod pe { 164 | use super::super::*; 165 | const FMT: &str = "pe"; 166 | 167 | #[test] 168 | fn x86() { 169 | for sz in ["small", "big"] { 170 | let res = tests::run_basic_test(sz, "x86", FMT); 171 | assert!(res.len() > 0); 172 | } 173 | } 174 | 175 | #[test] 176 | fn x64() { 177 | for sz in ["small", "big"] { 178 | let res = tests::run_basic_test(sz, "x64", FMT); 179 | assert!(res.len() > 0); 180 | } 181 | } 182 | 183 | // #[test] 184 | // fn arm32() { 185 | // for sz in ["small", "big"] { 186 | // let res = tests::run_basic_test(sz, "arm32", FMT); 187 | // assert!(res.len() > 0); 188 | // } 189 | // } 190 | #[test] 191 | fn arm64() { 192 | for sz in ["small", "big"] { 193 | let res = tests::run_basic_test(sz, "arm64", FMT); 194 | assert!(res.len() > 0); 195 | } 196 | } 197 | } 198 | 199 | mod elf { 200 | use super::super::*; 201 | const FMT: &str = "elf"; 202 | 203 | #[test] 204 | fn x86() { 205 | for sz in ["small", "big"] { 206 | let res = tests::run_basic_test(sz, "x86", FMT); 207 | assert!(res.len() > 0); 208 | } 209 | } 210 | 211 | #[test] 212 | fn x64() { 213 | for sz in ["small", "big"] { 214 | let res = tests::run_basic_test(sz, "x64", FMT); 215 | assert!(res.len() > 0); 216 | } 217 | } 218 | 219 | // #[test] 220 | // fn arm32() { 221 | // for sz in ["big", "small"] { 222 | // let res = tests::run_basic_test(sz, "arm32", FMT); 223 | // assert!(res.len() > 0); 224 | // } 225 | // } 226 | #[test] 227 | fn arm64() { 228 | for sz in ["small", "big"] { 229 | let res = tests::run_basic_test(sz, "arm64", FMT); 230 | assert!(res.len() > 0); 231 | } 232 | } 233 | } 234 | 235 | mod macho { 236 | use super::super::*; 237 | const FMT: &str = "macho"; 238 | 239 | #[test] 240 | fn x86() { 241 | for sz in vec!["small"] { 242 | let res = tests::run_basic_test(sz, "x86", FMT); 243 | assert!(res.len() > 0); 244 | } 245 | } 246 | 247 | #[test] 248 | fn x64() { 249 | for sz in vec!["small"] { 250 | let res = tests::run_basic_test(sz, "x64", FMT); 251 | assert!(res.len() > 0); 252 | } 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/section.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Borrow, fmt}; 2 | 3 | bitflags! { 4 | #[derive(Debug)] 5 | pub struct Permission: u8 6 | { 7 | const NONE = 0; 8 | const READABLE = 1; 9 | const WRITABLE = 2; 10 | const EXECUTABLE = 4; 11 | const ALL = Self::READABLE.bits() | Self::WRITABLE.bits() | Self::EXECUTABLE.bits(); 12 | } 13 | 14 | } 15 | 16 | impl Default for Permission { 17 | /// Return NONE as default 18 | fn default() -> Self { 19 | Permission::NONE 20 | } 21 | } 22 | 23 | #[derive(Debug)] 24 | #[derive(Default)] 25 | pub struct Section { 26 | pub start_address: u64, 27 | pub end_address: u64, 28 | pub name: Option, 29 | pub permission: Permission, 30 | pub data: Vec, 31 | } 32 | 33 | impl fmt::Display for Section { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | let name = self 36 | .name 37 | .as_ref() 38 | .unwrap_or(String::from("N/A").borrow()) 39 | .clone(); 40 | write!( 41 | f, 42 | "Section(name='{}', start={:#x}, sz={:#x}, permission={:?})", 43 | name, 44 | self.start_address, 45 | self.size(), 46 | self.permission 47 | ) 48 | } 49 | } 50 | 51 | impl Section { 52 | pub fn new(start_address: u64, end_address: u64) -> Self { 53 | assert!(start_address < end_address); 54 | let sz = (end_address - start_address) as usize; 55 | Self { 56 | start_address, 57 | end_address, 58 | name: None, 59 | permission: Permission::NONE, 60 | data: vec![0; sz], 61 | } 62 | } 63 | 64 | pub fn size(&self) -> usize { 65 | (self.end_address - self.start_address) as usize 66 | } 67 | 68 | pub fn name(self, name: &str) -> Self { 69 | Self { 70 | name: Some(name.to_string()), 71 | ..self 72 | } 73 | } 74 | 75 | pub fn data(self, data: Vec) -> Self { 76 | Self { data, ..self } 77 | } 78 | } 79 | 80 | 81 | 82 | impl From<&goblin::elf::section_header::SectionHeader> for Section { 83 | fn from(value: &goblin::elf::section_header::SectionHeader) -> Self { 84 | let mut perm = Permission::NONE; 85 | 86 | if value.is_executable() { 87 | perm |= Permission::READABLE | Permission::EXECUTABLE; 88 | } 89 | 90 | if value.is_writable() { 91 | perm |= Permission::READABLE | Permission::WRITABLE; 92 | } 93 | 94 | let sz = value.sh_size as usize; 95 | 96 | Self { 97 | start_address: value.sh_addr, 98 | end_address: value.sh_addr + sz as u64, 99 | permission: perm, 100 | name: None, 101 | data: vec![0; sz], 102 | } 103 | } 104 | } 105 | 106 | impl From<&goblin::mach::segment::Segment<'_>> for Section { 107 | fn from(value: &goblin::mach::segment::Segment) -> Self { 108 | let mut perm = Permission::READABLE; 109 | 110 | if value.flags & goblin::mach::constants::S_ATTR_PURE_INSTRUCTIONS == 0 111 | || value.flags & goblin::mach::constants::S_ATTR_SOME_INSTRUCTIONS == 0 112 | { 113 | perm |= Permission::EXECUTABLE; 114 | } 115 | 116 | let section_name = match std::str::from_utf8(&value.segname) { 117 | Ok(v) => String::from(v).replace('\0', ""), 118 | Err(_) => "".to_string(), 119 | }; 120 | 121 | let sz = value.vmsize as usize; 122 | 123 | Self { 124 | start_address: value.vmaddr, 125 | end_address: value.vmaddr + sz as u64, 126 | name: Some(section_name), 127 | permission: perm, 128 | data: vec![0; sz], 129 | } 130 | } 131 | } 132 | 133 | impl From<&goblin::pe::section_table::SectionTable> for Section { 134 | fn from(value: &goblin::pe::section_table::SectionTable) -> Self { 135 | let section_name = match std::str::from_utf8(&value.name) { 136 | Ok(v) => String::from(v).replace('\0', ""), 137 | Err(_) => String::new(), 138 | }; 139 | 140 | let mut perm = Permission::NONE; 141 | if value.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_READ != 0 { 142 | perm |= Permission::READABLE; 143 | } 144 | 145 | if value.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_WRITE != 0 { 146 | perm |= Permission::WRITABLE; 147 | } 148 | 149 | if value.characteristics & goblin::pe::section_table::IMAGE_SCN_MEM_EXECUTE != 0 { 150 | perm |= Permission::EXECUTABLE; 151 | } 152 | 153 | let sz = value.virtual_size as usize; 154 | 155 | Self { 156 | start_address: value.virtual_address as u64, 157 | end_address: (value.virtual_address + value.virtual_size) as u64, 158 | name: Some(section_name), 159 | permission: perm, 160 | data: vec![0; sz], 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/session.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::path::PathBuf; 3 | use std::sync::{Arc, Mutex}; 4 | use std::thread; 5 | 6 | use clap::ValueEnum; 7 | use colored::*; 8 | use log::{debug, info, warn, Level, LevelFilter, Metadata, Record}; 9 | 10 | use crate::common::GenericResult; 11 | use crate::cpu; 12 | use crate::engine::{DisassemblyEngine, DisassemblyEngineType}; 13 | use crate::format::{self, guess_file_format}; 14 | use crate::gadget::{ 15 | find_gadgets_from_position, get_all_valid_positions_and_length, Gadget, InstructionGroup, 16 | }; 17 | 18 | #[derive(std::fmt::Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)] 19 | pub enum RopProfileStrategy { 20 | #[default] 21 | /// Strategy Fast 22 | Fast, 23 | /// Strategy Complete 24 | Complete, 25 | } 26 | 27 | impl std::fmt::Display for RopProfileStrategy { 28 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 29 | std::fmt::Debug::fmt(self, f) 30 | } 31 | } 32 | 33 | pub struct ExecutableDetails { 34 | pub filepath: PathBuf, 35 | pub format: Box, 36 | pub cpu: Box, 37 | } 38 | 39 | impl std::fmt::Debug for ExecutableDetails { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | f.debug_struct("ExecutableDetails") 42 | .field("filepath", &self.filepath) 43 | .field("format", &self.format.format().to_string()) 44 | .field("cpu", &self.cpu.cpu_type().to_string()) 45 | .finish() 46 | } 47 | } 48 | 49 | impl std::fmt::Display for ExecutableDetails { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | write!( 52 | f, 53 | "Info({}, {}, Entry=0x{:x})", 54 | self.cpu.cpu_type(), 55 | self.format.format(), 56 | self.format.entry_point() 57 | ) 58 | } 59 | } 60 | 61 | impl Default for ExecutableDetails { 62 | fn default() -> Self { 63 | ExecutableDetails { 64 | filepath: PathBuf::new(), 65 | cpu: Box::new(cpu::x86::X86 {}), 66 | format: Box::new(format::pe::Pe::default()), 67 | } 68 | } 69 | } 70 | 71 | impl ExecutableDetails { 72 | pub fn new(filepath: PathBuf) -> Self { 73 | let fpath = filepath.clone(); 74 | let format = guess_file_format(&fpath).unwrap(); 75 | 76 | let cpu: Box = match format.cpu_type() { 77 | cpu::CpuType::X86 => Box::new(cpu::x86::X86 {}), 78 | cpu::CpuType::X64 => Box::new(cpu::x86::X64 {}), 79 | cpu::CpuType::ARM64 => Box::new(cpu::arm::Arm64 {}), 80 | cpu::CpuType::ARM => Box::new(cpu::arm::Arm {}), 81 | _ => panic!("CPU type is invalid"), 82 | }; 83 | // let cpu = Box::new( Cpu::from(format.cpu_type()) ); 84 | 85 | ExecutableDetails { 86 | filepath: fpath, 87 | cpu, 88 | format, 89 | } 90 | } 91 | 92 | pub fn is_64b(&self) -> bool { 93 | self.cpu.ptrsize() == 8 94 | } 95 | } 96 | 97 | #[derive(Debug, Clone, Default)] 98 | pub enum RopGadgetOutput { 99 | #[default] 100 | None, 101 | Console, 102 | File(PathBuf), 103 | } 104 | 105 | struct RpLogger; 106 | 107 | impl log::Log for RpLogger { 108 | fn enabled(&self, metadata: &Metadata) -> bool { 109 | metadata.level() <= Level::Trace 110 | } 111 | 112 | fn log(&self, record: &Record) { 113 | if self.enabled(record.metadata()) { 114 | let level = match record.level().to_string().as_str() { 115 | "ERROR" => "ERROR".red(), 116 | "WARN" => "WARN".magenta(), 117 | "INFO" => "INFO".green(), 118 | "DEBUG" => "DEBUG".cyan(), 119 | _ => "TRACE".bold(), 120 | }; 121 | 122 | println!("[{}] - {}", level, record.args()); 123 | } 124 | } 125 | 126 | fn flush(&self) {} 127 | } 128 | 129 | #[derive(Debug)] 130 | pub struct Session { 131 | // 132 | // session required information 133 | // 134 | pub info: ExecutableDetails, 135 | pub nb_thread: u8, 136 | pub verbosity: LevelFilter, 137 | pub output: RopGadgetOutput, 138 | 139 | // 140 | // misc details about the executable file (filled by ) 141 | // 142 | 143 | // pub file_format: format::FileFormat, 144 | 145 | // 146 | // the info need to build, store and show the ropgadgets 147 | // 148 | pub engine_type: DisassemblyEngineType, 149 | pub max_gadget_length: usize, 150 | pub gadgets: Mutex>, 151 | pub unique_only: bool, 152 | pub use_color: bool, 153 | pub gadget_types: Vec, 154 | pub profile_type: RopProfileStrategy, 155 | } 156 | 157 | impl Session { 158 | pub fn new(filepath: PathBuf) -> Self { 159 | Session { 160 | info: ExecutableDetails::new(filepath), 161 | ..Default::default() 162 | } 163 | } 164 | 165 | pub fn nb_thread(self, nb_thread: u8) -> Self { 166 | Self { nb_thread, ..self } 167 | } 168 | 169 | pub fn output(self, new_output: RopGadgetOutput) -> Self { 170 | Self { 171 | output: new_output, 172 | ..self 173 | } 174 | } 175 | 176 | pub fn unique_only(self, unique_only: bool) -> Self { 177 | Self { 178 | unique_only, 179 | ..self 180 | } 181 | } 182 | 183 | pub fn use_color(self, use_color: bool) -> Self { 184 | Self { use_color, ..self } 185 | } 186 | 187 | pub fn verbosity(self, verbosity: LevelFilter) -> Self { 188 | Self { verbosity, ..self } 189 | } 190 | 191 | pub fn filepath(&self) -> &PathBuf { 192 | &self.info.filepath 193 | } 194 | } 195 | 196 | impl Default for Session { 197 | fn default() -> Self { 198 | Session { 199 | verbosity: LevelFilter::Off, 200 | nb_thread: 4, 201 | output: RopGadgetOutput::None, 202 | unique_only: true, 203 | use_color: true, 204 | max_gadget_length: 6, 205 | gadget_types: vec![InstructionGroup::Ret], 206 | profile_type: RopProfileStrategy::Fast, 207 | gadgets: Mutex::new(Vec::new()), 208 | engine_type: DisassemblyEngineType::Capstone, 209 | info: ExecutableDetails::default(), 210 | } 211 | } 212 | } 213 | 214 | // impl std::fmt::Debug for Session { 215 | // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 216 | // f.debug_struct("Session") 217 | // .field("path", &self.filepath()) 218 | // .field("format", &self.info.format.format().to_string()) 219 | // .field("cpu", &self.info.cpu.cpu_type().to_string()) 220 | // .finish() 221 | // } 222 | // } 223 | 224 | impl std::fmt::Display for Session { 225 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 226 | let gadget_types: Vec = self.gadget_types.iter().map(|x| x.to_string()).collect(); 227 | write!( 228 | f, 229 | "Session(File='{}', {}, Profile={}, GadgetTypes=[{}])", 230 | self.filepath().to_str().unwrap(), 231 | self.info, 232 | self.profile_type, 233 | gadget_types.join(", "), 234 | ) 235 | } 236 | } 237 | 238 | /// 239 | /// This function manages the thread pool to look for gadget 240 | /// 241 | pub fn find_gadgets(session: Arc) -> GenericResult<()> { 242 | let info = &session.info; 243 | let number_of_sections = info.format.sections().len(); 244 | let nb_thread = session.nb_thread as usize; 245 | 246 | debug!("Using {nb_thread} threads over {number_of_sections} section(s) of executable code..."); 247 | 248 | // 249 | // Multithread parsing of each section 250 | // 251 | for section_idx in 0..number_of_sections { 252 | if info.format.sections().get(section_idx).is_none() { 253 | continue; 254 | } 255 | 256 | let section = info.format.sections().get(section_idx).unwrap(); 257 | let chunk_size = section.data.len() / nb_thread; 258 | 259 | // 260 | // Fill the thread pool 261 | // 262 | let mut threads: Vec>> = Vec::new(); 263 | let mut pos = 0; 264 | let mut thread_pool_size = 0; 265 | let mut force_flush = false; 266 | 267 | loop { 268 | // 269 | // Empty the thread pool if necessary 270 | // 271 | if thread_pool_size == nb_thread || force_flush { 272 | for curthread in threads { 273 | debug!("Joining {:?}...", curthread.thread().id()); 274 | let gadgets = curthread.join().unwrap(); 275 | { 276 | let mut data = session.gadgets.lock().unwrap(); 277 | data.extend(gadgets); 278 | } 279 | } 280 | 281 | threads = Vec::new(); 282 | thread_pool_size = 0; 283 | 284 | if force_flush { 285 | break; 286 | } 287 | } 288 | 289 | // 290 | // Is there still some data to parse? 291 | // 292 | if pos >= section.data.len() { 293 | force_flush = true; 294 | continue; 295 | } 296 | 297 | // 298 | // If so, spawn more workers 299 | // 300 | let rc_session = Arc::clone(&session); 301 | let thread = thread::spawn(move || thread_worker(rc_session, section_idx, pos)); 302 | debug!( 303 | "Spawning {:?} (pos={} section_index={})...", 304 | thread.thread().id(), 305 | pos, 306 | section_idx 307 | ); 308 | threads.push(thread); 309 | thread_pool_size += 1; 310 | pos += chunk_size; 311 | } 312 | } 313 | 314 | info!( 315 | "Total gadgets found => {}", 316 | session.gadgets.lock().unwrap().len() 317 | ); 318 | Ok(()) 319 | } 320 | 321 | /// 322 | /// Worker routine to search for gadgets 323 | /// 324 | fn thread_worker(session: Arc, index: usize, cursor: usize) -> Vec { 325 | let cpu = session.info.cpu.as_ref(); 326 | let engine = DisassemblyEngine::new(&session.engine_type, cpu); 327 | debug!( 328 | "{:?}: Initialized engine {} for {:?}", 329 | thread::current().id(), 330 | engine, 331 | cpu.cpu_type() 332 | ); 333 | 334 | let mut gadgets: Vec = Vec::new(); 335 | let sections = session.info.format.sections(); 336 | if let Some(section) = sections.get(index) { 337 | let section_name = section 338 | .name 339 | .as_ref() 340 | .unwrap_or(String::from("N/A").borrow()) 341 | .clone(); 342 | 343 | debug!( 344 | "{:?}: Processing section '{}'", 345 | thread::current().id(), 346 | section_name 347 | ); 348 | 349 | let cpu = &session.info.cpu; 350 | let disass = engine.disassembler.as_ref(); 351 | 352 | for (pos, len) in 353 | get_all_valid_positions_and_length(&session, cpu, section, cursor).unwrap() 354 | { 355 | debug!( 356 | "{:?}: Processing Section {}[..{:x}+{:x}] (size={:x})", 357 | thread::current().id(), 358 | section_name, 359 | pos, 360 | len, 361 | section.size(), 362 | ); 363 | 364 | let res = find_gadgets_from_position(session.clone(), disass, section, pos, len, cpu); 365 | if res.is_ok() { 366 | let mut gadget = res.unwrap(); 367 | gadgets.append(&mut gadget); 368 | } 369 | } 370 | 371 | debug!( 372 | "{:?}: Finished processing section '{}'", 373 | thread::current().id(), 374 | section_name, 375 | ); 376 | } else { 377 | warn!( 378 | "{:?}: No section at index {}, ending...", 379 | thread::current().id(), 380 | index, 381 | ); 382 | } 383 | 384 | gadgets 385 | } 386 | -------------------------------------------------------------------------------- /tests/bin/big-arm32.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-arm32.elf -------------------------------------------------------------------------------- /tests/bin/big-arm32.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-arm32.pe -------------------------------------------------------------------------------- /tests/bin/big-arm64.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-arm64.elf -------------------------------------------------------------------------------- /tests/bin/big-arm64.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-arm64.pe -------------------------------------------------------------------------------- /tests/bin/big-x64.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-x64.elf -------------------------------------------------------------------------------- /tests/bin/big-x64.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-x64.pe -------------------------------------------------------------------------------- /tests/bin/big-x86.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-x86.elf -------------------------------------------------------------------------------- /tests/bin/big-x86.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/big-x86.pe -------------------------------------------------------------------------------- /tests/bin/small-arm32.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-arm32.elf -------------------------------------------------------------------------------- /tests/bin/small-arm32.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-arm32.pe -------------------------------------------------------------------------------- /tests/bin/small-arm64.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-arm64.elf -------------------------------------------------------------------------------- /tests/bin/small-arm64.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-arm64.pe -------------------------------------------------------------------------------- /tests/bin/small-x64.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x64.elf -------------------------------------------------------------------------------- /tests/bin/small-x64.macho: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x64.macho -------------------------------------------------------------------------------- /tests/bin/small-x64.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x64.pe -------------------------------------------------------------------------------- /tests/bin/small-x86.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x86.elf -------------------------------------------------------------------------------- /tests/bin/small-x86.macho: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x86.macho -------------------------------------------------------------------------------- /tests/bin/small-x86.pe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/ropgadget-rs/f755d1de4ec20d25cc8ebab428c3f426cf73647c/tests/bin/small-x86.pe --------------------------------------------------------------------------------