├── .dockerignore ├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── cd.yml │ ├── ci.yml │ ├── docker.yml │ ├── flake-update.yml │ └── website.yml ├── .gitignore ├── .well-known └── funding-manifest-urls ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASE.md ├── SECURITY.md ├── cliff.toml ├── codecov.yml ├── committed.toml ├── deny.toml ├── examples └── demo.rs ├── flake.lock ├── flake.nix ├── rustfmt.toml ├── src ├── app.rs ├── args.rs ├── elf │ ├── dynamic.rs │ ├── header.rs │ ├── mod.rs │ ├── notes.rs │ ├── relocations.rs │ └── symbols.rs ├── error.rs ├── file.rs ├── lib.rs ├── main.rs ├── prelude.rs ├── tracer.rs └── tui │ ├── command.rs │ ├── event.rs │ ├── mod.rs │ ├── state.rs │ ├── ui.rs │ └── widgets │ ├── list.rs │ ├── logo.rs │ └── mod.rs ├── tests └── app.rs ├── typos.toml └── website ├── .gitignore ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public ├── CNAME ├── apple-touch-icon.png └── favicon.svg ├── src ├── assets │ ├── binsider-logo-dark.png │ ├── binsider-logo-light.png │ ├── binsider-social-card.png │ ├── binsider-text-dark.png │ ├── binsider-text-light.png │ └── demo │ │ ├── binsider-dynamic-analysis.gif │ │ ├── binsider-general-analysis.gif │ │ ├── binsider-hexdump.gif │ │ ├── binsider-static-analysis.gif │ │ └── binsider-strings.gif ├── components │ ├── Footer.astro │ ├── Footer │ │ ├── Icon.astro │ │ ├── Stars.astro │ │ ├── Stat.astro │ │ └── Version.astro │ ├── Head.astro │ ├── Header.astro │ ├── Hero.astro │ ├── Logo.astro │ └── Video.astro ├── content │ ├── assets │ │ ├── analyze-library.gif │ │ ├── blog │ │ │ ├── 0.2.0-libs.png │ │ │ ├── 0.2.0-static.gif │ │ │ ├── 20240917_122011.jpg │ │ │ └── umami-2024927.png │ │ ├── dynamic-analysis.gif │ │ ├── dynamic-summary.jpg │ │ ├── dynamic.jpg │ │ ├── elf-overview.svg │ │ ├── file-headers.jpg │ │ ├── file-information.jpg │ │ ├── general.jpg │ │ ├── hexdump-modify.gif │ │ ├── hexdump.gif │ │ ├── hexdump.jpg │ │ ├── library-path.gif │ │ ├── linked-libraries.jpg │ │ ├── notes.jpg │ │ ├── quickstart.gif │ │ ├── static-table.gif │ │ ├── static.jpg │ │ ├── strings.gif │ │ └── strings.jpg │ ├── config.ts │ └── docs │ │ ├── 404.md │ │ ├── blog │ │ └── v0.2.0.md │ │ ├── extras │ │ └── library.md │ │ ├── getting-started │ │ └── index.mdx │ │ ├── index.mdx │ │ ├── installation │ │ ├── alpine-linux.md │ │ ├── arch-linux.md │ │ ├── binary-releases.md │ │ ├── build-from-source.md │ │ ├── crates-io.md │ │ ├── docker.md │ │ └── other.md │ │ ├── pricing.md │ │ └── usage │ │ ├── dynamic-analysis.md │ │ ├── general-analysis.md │ │ ├── hexdump.md │ │ ├── static-analysis.md │ │ └── strings.md ├── env.d.ts ├── slider.js ├── styles │ └── custom.css └── tailwind.css ├── tailwind.config.mjs ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | /.git/ 3 | /.github/ 4 | /target/ 5 | /website/ 6 | 7 | # Files 8 | .editorconfig 9 | .gitignore 10 | *.md 11 | codecov.yml 12 | Dockerfile 13 | LICENSE* 14 | rustfmt.toml 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # configuration for https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.rs] 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [args.rs] 10 | indent_size = unset 11 | 12 | [*.yml] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ 2 | # for more info about CODEOWNERS file 3 | 4 | # It uses the same pattern rule for gitignore file 5 | # https://git-scm.com/docs/gitignore#_pattern_format 6 | 7 | # Core 8 | * @orhun 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: orhun 2 | patreon: orhunp 3 | buy_me_a_coffee: orhun 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐛 3 | about: Report a bug to help us improve 4 | title: "" 5 | labels: "bug" 6 | assignees: "orhun" 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | 13 | **To reproduce** 14 | 15 | 22 | 23 | **Expected behavior** 24 | 25 | 26 | 27 | **Screenshots / Logs** 28 | 29 | 30 | 31 | **Software information** 32 | 33 | 34 | 35 | - Operating system: 36 | - Rust version: 37 | - Project version: 38 | 39 | **Additional context** 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 😼 3 | about: Make a suggestion for the project 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "orhun" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | 17 | **Describe alternatives you've considered** 18 | 19 | 20 | 21 | **Additional context** 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description of change 4 | 5 | 6 | 7 | 8 | ## How has this been tested? (if applicable) 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | groups: 12 | npm-dependencies: 13 | patterns: ["*"] 14 | - package-ecosystem: "cargo" 15 | directory: "/" 16 | schedule: 17 | interval: "weekly" 18 | groups: 19 | cargo-dependencies: 20 | patterns: ["*"] 21 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | publish-github: 10 | name: Publish for ${{ matrix.build.OS }} (${{ matrix.build.TARGET }}) 11 | runs-on: ${{ matrix.build.OS }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | build: 16 | - { 17 | OS: ubuntu-22.04, 18 | TOOLCHAIN: stable, 19 | TARGET: x86_64-unknown-linux-gnu, 20 | ARGS: "", 21 | } 22 | - { 23 | OS: ubuntu-22.04, 24 | TOOLCHAIN: stable, 25 | TARGET: x86_64-unknown-linux-musl, 26 | ARGS: "", 27 | } 28 | - { 29 | OS: ubuntu-22.04, 30 | TOOLCHAIN: stable, 31 | TARGET: aarch64-unknown-linux-gnu, 32 | ARGS: "", 33 | } 34 | - { 35 | OS: ubuntu-22.04, 36 | TOOLCHAIN: stable, 37 | TARGET: riscv64gc-unknown-linux-gnu, 38 | ARGS: "", 39 | } 40 | - { 41 | OS: ubuntu-22.04, 42 | TOOLCHAIN: stable, 43 | TARGET: i686-unknown-linux-gnu, 44 | ARGS: "--no-default-features", 45 | } 46 | - { 47 | OS: ubuntu-22.04, 48 | TOOLCHAIN: stable, 49 | TARGET: i686-unknown-linux-musl, 50 | ARGS: "--no-default-features", 51 | } 52 | - { 53 | OS: ubuntu-22.04, 54 | TOOLCHAIN: stable, 55 | TARGET: aarch64-unknown-linux-musl, 56 | ARGS: "--no-default-features", 57 | } 58 | - { 59 | OS: ubuntu-22.04, 60 | TOOLCHAIN: stable, 61 | TARGET: armv5te-unknown-linux-gnueabi, 62 | ARGS: "--no-default-features", 63 | } 64 | - { 65 | OS: ubuntu-22.04, 66 | TOOLCHAIN: stable, 67 | TARGET: armv7-unknown-linux-gnueabihf, 68 | ARGS: "--no-default-features", 69 | } 70 | - { 71 | OS: ubuntu-22.04, 72 | TOOLCHAIN: stable, 73 | TARGET: arm-unknown-linux-gnueabi, 74 | ARGS: "--no-default-features", 75 | } 76 | - { 77 | OS: ubuntu-22.04, 78 | TOOLCHAIN: stable, 79 | TARGET: arm-unknown-linux-gnueabihf, 80 | ARGS: "--no-default-features", 81 | } 82 | - { 83 | OS: ubuntu-22.04, 84 | TOOLCHAIN: stable, 85 | TARGET: powerpc64le-unknown-linux-gnu, 86 | ARGS: "--no-default-features", 87 | } 88 | - { 89 | OS: windows-2022, 90 | TOOLCHAIN: stable, 91 | TARGET: x86_64-pc-windows-msvc, 92 | ARGS: "--no-default-features", 93 | } 94 | - { 95 | OS: macos-14, 96 | TOOLCHAIN: stable, 97 | TARGET: x86_64-apple-darwin, 98 | ARGS: "--no-default-features", 99 | } 100 | - { 101 | OS: macos-14, 102 | TOOLCHAIN: stable, 103 | TARGET: aarch64-apple-darwin, 104 | ARGS: "--no-default-features", 105 | } 106 | steps: 107 | - name: Checkout the repository 108 | uses: actions/checkout@v4 109 | 110 | - name: Set the release version 111 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 112 | 113 | - name: Install Rust toolchain 114 | uses: actions-rs/toolchain@v1 115 | with: 116 | toolchain: ${{ matrix.build.TOOLCHAIN }} 117 | target: ${{ matrix.build.TARGET }} 118 | override: true 119 | 120 | - name: Cache Cargo dependencies 121 | uses: Swatinem/rust-cache@v2 122 | 123 | - name: Build 124 | uses: actions-rs/cargo@v1 125 | with: 126 | use-cross: ${{ matrix.build.OS != 'windows-2022' }} 127 | command: build 128 | args: --release --locked --target ${{ matrix.build.TARGET }} ${{ matrix.build.ARGS }} 129 | 130 | - name: Prepare release assets 131 | shell: bash 132 | run: | 133 | mkdir -p release 134 | cp {LICENSE-MIT,LICENSE-APACHE,README.md,CHANGELOG.md} release/ 135 | if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then 136 | cp target/${{ matrix.build.TARGET }}/release/binsider.exe release/ 137 | else 138 | cp target/${{ matrix.build.TARGET }}/release/binsider release/ 139 | fi 140 | mv release/ binsider-${{env.RELEASE_VERSION}}/ 141 | 142 | - name: Create release artifacts 143 | shell: bash 144 | run: | 145 | if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then 146 | 7z a -tzip "binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}.zip" \ 147 | binsider-${{ env.RELEASE_VERSION }}/ 148 | else 149 | tar -czvf binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}.tar.gz \ 150 | binsider-${{ env.RELEASE_VERSION }}/ 151 | shasum -a 512 binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}.tar.gz \ 152 | > binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}.tar.gz.sha512 153 | fi 154 | 155 | - name: Sign the release 156 | if: matrix.build.OS != 'windows-2022' 157 | run: | 158 | echo "${{ secrets.GPG_RELEASE_KEY }}" | base64 --decode > private.key 159 | echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --pinentry-mode=loopback \ 160 | --passphrase-fd 0 --import private.key 161 | echo "${{secrets.GPG_PASSPHRASE}}" | gpg --pinentry-mode=loopback \ 162 | --passphrase-fd 0 --detach-sign \ 163 | binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}.tar.gz 164 | 165 | - name: Generate a changelog 166 | uses: orhun/git-cliff-action@v4 167 | id: git-cliff 168 | with: 169 | args: --latest --github-repo ${{ github.repository }} 170 | env: 171 | OUTPUT: CHANGES.md 172 | 173 | - name: Upload the binary releases 174 | uses: svenstaro/upload-release-action@v2 175 | with: 176 | file: binsider-${{ env.RELEASE_VERSION }}-${{ matrix.build.TARGET }}* 177 | file_glob: true 178 | overwrite: true 179 | tag: ${{ github.ref }} 180 | release_name: "Release v${{ env.RELEASE_VERSION }}" 181 | body: ${{ steps.git-cliff.outputs.content }} 182 | repo_token: ${{ secrets.RELEASE_TOKEN }} 183 | 184 | publish-crates-io: 185 | name: Publish on crates.io 186 | needs: publish-github 187 | runs-on: ubuntu-latest 188 | steps: 189 | - name: Checkout the repository 190 | uses: actions/checkout@v4 191 | 192 | - name: Install Rust stable 193 | uses: dtolnay/rust-toolchain@stable 194 | 195 | - name: Publish 196 | run: cargo publish --locked --token ${{ secrets.CARGO_REGISTRY_TOKEN }} 197 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 0 * * 0" 12 | 13 | jobs: 14 | build: 15 | name: Build on ${{ matrix.build.OS }} (${{ matrix.build.TARGET }}) 16 | runs-on: ${{ matrix.build.OS }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | build: 21 | - { 22 | OS: ubuntu-22.04, 23 | TOOLCHAIN: stable, 24 | TARGET: x86_64-unknown-linux-gnu, 25 | ALL_FEATURES: true, 26 | } 27 | - { 28 | OS: ubuntu-22.04, 29 | TOOLCHAIN: stable, 30 | TARGET: x86_64-unknown-linux-musl, 31 | ALL_FEATURES: true, 32 | } 33 | - { 34 | OS: ubuntu-22.04, 35 | TOOLCHAIN: stable, 36 | TARGET: aarch64-unknown-linux-gnu, 37 | ALL_FEATURES: true, 38 | } 39 | - { 40 | OS: ubuntu-22.04, 41 | TOOLCHAIN: stable, 42 | TARGET: riscv64gc-unknown-linux-gnu, 43 | ALL_FEATURES: true, 44 | } 45 | - { 46 | OS: ubuntu-22.04, 47 | TOOLCHAIN: stable, 48 | TARGET: i686-unknown-linux-gnu, 49 | ALL_FEATURES: false, 50 | } 51 | - { 52 | OS: ubuntu-22.04, 53 | TOOLCHAIN: stable, 54 | TARGET: i686-unknown-linux-musl, 55 | ALL_FEATURES: false, 56 | } 57 | - { 58 | OS: ubuntu-22.04, 59 | TOOLCHAIN: stable, 60 | TARGET: aarch64-unknown-linux-musl, 61 | ALL_FEATURES: false, 62 | } 63 | - { 64 | OS: ubuntu-22.04, 65 | TOOLCHAIN: stable, 66 | TARGET: armv5te-unknown-linux-gnueabi, 67 | ALL_FEATURES: false, 68 | } 69 | - { 70 | OS: ubuntu-22.04, 71 | TOOLCHAIN: stable, 72 | TARGET: armv7-unknown-linux-gnueabihf, 73 | ALL_FEATURES: false, 74 | } 75 | - { 76 | OS: ubuntu-22.04, 77 | TOOLCHAIN: stable, 78 | TARGET: arm-unknown-linux-gnueabi, 79 | ALL_FEATURES: false, 80 | } 81 | - { 82 | OS: ubuntu-22.04, 83 | TOOLCHAIN: stable, 84 | TARGET: arm-unknown-linux-gnueabihf, 85 | ALL_FEATURES: false, 86 | } 87 | - { 88 | OS: ubuntu-22.04, 89 | TOOLCHAIN: stable, 90 | TARGET: powerpc64le-unknown-linux-gnu, 91 | ALL_FEATURES: false, 92 | } 93 | - { 94 | OS: windows-2022, 95 | TOOLCHAIN: stable, 96 | TARGET: x86_64-pc-windows-msvc, 97 | ALL_FEATURES: false, 98 | } 99 | - { 100 | OS: macos-14, 101 | TOOLCHAIN: stable, 102 | TARGET: x86_64-apple-darwin, 103 | ALL_FEATURES: false, 104 | } 105 | - { 106 | OS: macos-14, 107 | TOOLCHAIN: stable, 108 | TARGET: aarch64-apple-darwin, 109 | ALL_FEATURES: false, 110 | } 111 | steps: 112 | - name: Checkout the repository 113 | uses: actions/checkout@v4 114 | 115 | - name: Install dependencies 116 | if: matrix.build.TARGET == 'x86_64-unknown-linux-musl' 117 | run: | 118 | sudo apt-get update 119 | sudo apt-get install -y \ 120 | --no-install-recommends \ 121 | --allow-unauthenticated \ 122 | musl-tools 123 | 124 | - name: Install Rust toolchain 125 | uses: dtolnay/rust-toolchain@master 126 | with: 127 | toolchain: ${{ matrix.build.TOOLCHAIN }} 128 | targets: ${{ matrix.build.TARGET }} 129 | 130 | - name: Cache Cargo dependencies 131 | uses: Swatinem/rust-cache@v2 132 | 133 | - name: Build the project 134 | shell: bash 135 | run: | 136 | if [ "${{ matrix.build.ALL_FEATURES }}" = true ]; then 137 | cargo build --locked --verbose 138 | else 139 | cargo build --no-default-features --locked --verbose 140 | fi 141 | 142 | - name: Upload artifacts 143 | uses: actions/upload-artifact@v4 144 | with: 145 | name: binsider-${{ matrix.build.TARGET }}-assets 146 | path: target/debug/binsider* 147 | 148 | nix-flake: 149 | name: Build Nix flake 150 | runs-on: ubuntu-latest 151 | permissions: 152 | actions: write 153 | steps: 154 | - name: Checkout the repository 155 | uses: actions/checkout@v4 156 | 157 | - name: Install Nix 158 | uses: nixbuild/nix-quick-install-action@v28 159 | 160 | - name: Restore and cache Nix store 161 | uses: nix-community/cache-nix-action@v5 162 | with: 163 | primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.nix', 'flake.lock', 'Cargo.lock', 'Cargo.toml') }} 164 | restore-prefixes-first-match: nix-${{ runner.os }}- 165 | gc-max-store-size-linux: 1073741824 166 | purge: true 167 | purge-prefixes: nix-${{ runner.os }}- 168 | purge-created: 0 169 | purge-primary-key: never 170 | 171 | - name: Check Nix flake 172 | run: nix flake check --all-systems 173 | 174 | test: 175 | name: Test 176 | runs-on: ubuntu-latest 177 | steps: 178 | - name: Checkout the repository 179 | uses: actions/checkout@v4 180 | 181 | - name: Install Rust toolchain 182 | uses: dtolnay/rust-toolchain@stable 183 | 184 | - name: Install cargo-llvm-cov 185 | uses: taiki-e/install-action@cargo-llvm-cov 186 | 187 | - name: Cache Cargo dependencies 188 | uses: Swatinem/rust-cache@v2 189 | 190 | - name: Generate code coverage 191 | run: | 192 | cargo build 193 | cargo llvm-cov --lcov --output-path lcov.info --lib 194 | env: 195 | OUT_DIR: target 196 | 197 | - name: Upload coverage to Codecov 198 | uses: codecov/codecov-action@v5 199 | with: 200 | name: code-coverage-report 201 | files: lcov.info 202 | fail_ci_if_error: true 203 | verbose: true 204 | env: 205 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 206 | 207 | commit-format: 208 | name: Commit format 209 | runs-on: ubuntu-latest 210 | steps: 211 | - name: Checkout the repository 212 | if: github.event_name != 'pull_request' 213 | uses: actions/checkout@v4 214 | 215 | - name: Checkout the repository 216 | if: github.event_name == 'pull_request' 217 | uses: actions/checkout@v4 218 | with: 219 | ref: ${{ github.event.pull_request.head.sha }} 220 | 221 | - name: Run committed 222 | uses: crate-ci/committed@master 223 | with: 224 | args: "-vv" 225 | commits: "HEAD" 226 | 227 | code-format: 228 | name: Code format 229 | runs-on: ubuntu-latest 230 | steps: 231 | - name: Checkout the repository 232 | uses: actions/checkout@v4 233 | 234 | - name: Install Rust toolchain 235 | uses: dtolnay/rust-toolchain@stable 236 | with: 237 | components: rustfmt 238 | 239 | - name: Check code formatting 240 | run: cargo fmt --all -- --check 241 | 242 | - name: Cache Cargo dependencies 243 | uses: Swatinem/rust-cache@v2 244 | 245 | - name: Install editorconfig-checker 246 | uses: editorconfig-checker/action-editorconfig-checker@main 247 | 248 | - name: Check file formatting 249 | run: editorconfig-checker 250 | 251 | clippy: 252 | name: Clippy 253 | runs-on: ubuntu-latest 254 | steps: 255 | - name: Checkout the repository 256 | uses: actions/checkout@v4 257 | 258 | - name: Install Rust toolchain 259 | uses: dtolnay/rust-toolchain@stable 260 | with: 261 | components: clippy 262 | 263 | - name: Cache Cargo dependencies 264 | uses: Swatinem/rust-cache@v2 265 | 266 | - name: Check lints 267 | run: cargo clippy -- -D warnings 268 | 269 | deny: 270 | name: Deny 271 | runs-on: ubuntu-latest 272 | steps: 273 | - name: Checkout the repository 274 | uses: actions/checkout@v4 275 | 276 | - name: Run cargo-deny 277 | uses: EmbarkStudios/cargo-deny-action@v2 278 | with: 279 | command: check all 280 | 281 | links: 282 | name: Links 283 | runs-on: ubuntu-latest 284 | steps: 285 | - name: Checkout the repository 286 | uses: actions/checkout@v4 287 | 288 | - name: Run lychee 289 | uses: lycheeverse/lychee-action@v1 290 | with: 291 | args: -v *.md 292 | env: 293 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 294 | 295 | typos: 296 | name: Typos 297 | runs-on: ubuntu-latest 298 | steps: 299 | - name: Checkout the repository 300 | uses: actions/checkout@v4 301 | 302 | - name: Check typos 303 | uses: crate-ci/typos@master 304 | 305 | msrv: 306 | name: MSRV 307 | runs-on: ubuntu-latest 308 | steps: 309 | - name: Checkout the repository 310 | uses: actions/checkout@v4 311 | 312 | - name: Install cargo-binstall 313 | uses: taiki-e/install-action@cargo-binstall 314 | 315 | - name: Install cargo-msrv 316 | run: cargo binstall -y --force cargo-msrv 317 | 318 | - name: Run cargo-msrv 319 | run: cargo msrv --output-format json verify | tail -n 1 | jq --exit-status '.success' 320 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*.*.*" 9 | pull_request: 10 | branches: 11 | - main 12 | schedule: 13 | - cron: "0 0 * * 0" 14 | 15 | jobs: 16 | docker: 17 | name: Build and Push 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Meta 24 | id: meta 25 | uses: docker/metadata-action@v5 26 | with: 27 | images: | 28 | orhunp/binsider 29 | ghcr.io/${{ github.repository_owner }}/binsider 30 | tags: | 31 | type=schedule 32 | type=ref,event=branch 33 | type=ref,event=pr 34 | type=sha 35 | type=raw,value=latest 36 | type=semver,pattern={{version}} 37 | 38 | - name: Set up Docker Buildx 39 | id: buildx 40 | uses: docker/setup-buildx-action@v3 41 | 42 | - name: Cache Docker layers 43 | uses: actions/cache@v4 44 | with: 45 | path: /tmp/.buildx-cache 46 | key: ${{ runner.os }}-buildx-${{ github.sha }} 47 | restore-keys: | 48 | ${{ runner.os }}-buildx- 49 | 50 | - name: Login to Docker Hub 51 | if: github.event_name != 'pull_request' 52 | uses: docker/login-action@v3 53 | with: 54 | username: orhunp 55 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 56 | 57 | - name: Login to GHCR 58 | if: github.event_name != 'pull_request' 59 | uses: docker/login-action@v3 60 | with: 61 | registry: ghcr.io 62 | username: ${{ github.repository_owner }} 63 | password: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | - name: Build and push 66 | id: docker_build 67 | uses: docker/build-push-action@v6 68 | with: 69 | context: ./ 70 | file: ./Dockerfile 71 | platforms: linux/amd64 72 | builder: ${{ steps.buildx.outputs.name }} 73 | push: ${{ github.event_name != 'pull_request' }} 74 | tags: ${{ steps.meta.outputs.tags }} 75 | sbom: true 76 | provenance: true 77 | labels: ${{ steps.meta.outputs.labels }} 78 | cache-from: type=local,src=/tmp/.buildx-cache 79 | cache-to: type=local,dest=/tmp/.buildx-cache 80 | 81 | - name: Scan the image 82 | uses: anchore/sbom-action@v0 83 | with: 84 | image: ghcr.io/${{ github.repository_owner }}/binsider 85 | 86 | - name: Image digest 87 | run: echo ${{ steps.docker_build.outputs.digest }} 88 | -------------------------------------------------------------------------------- /.github/workflows/flake-update.yml: -------------------------------------------------------------------------------- 1 | name: Update Nix flake 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "0 0 * * 0" # weekly 6 | jobs: 7 | update-flake: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | steps: 13 | - name: Checkout the repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Install Nix 17 | uses: nixbuild/nix-quick-install-action@v28 18 | 19 | - name: Update flake.lock 20 | uses: DeterminateSystems/update-flake-lock@v24 21 | with: 22 | commit-msg: "chore(deps): bump flake.lock" 23 | pr-title: "chore(deps): bump flake.lock" 24 | pr-labels: | 25 | dependencies 26 | github_actions 27 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*.*.*" 9 | pull_request: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | links: 25 | name: Links 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Run lychee 31 | uses: lycheeverse/lychee-action@v1 32 | with: 33 | args: -v website/src/content/docs/ 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | build: 38 | name: Build 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | - name: Install, build, and upload 44 | uses: withastro/action@v3 45 | with: 46 | path: ./website 47 | 48 | deploy: 49 | name: Deploy 50 | if: ${{ github.event_name != 'pull_request' }} 51 | needs: build 52 | runs-on: ubuntu-latest 53 | environment: 54 | name: github-pages 55 | url: ${{ steps.deployment.outputs.page_url }} 56 | steps: 57 | - name: Deploy to GitHub Pages 58 | id: deployment 59 | uses: actions/deploy-pages@v4 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files and executables 2 | /target/ 3 | 4 | # Backup files generated by rustfmt 5 | **/*.rs.bk 6 | 7 | # Nix result symlink 8 | /result 9 | -------------------------------------------------------------------------------- /.well-known/funding-manifest-urls: -------------------------------------------------------------------------------- 1 | https://orhun.dev/funding.json 2 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | Here is an architectural diagram of `binsider` which should help in understanding the various components and how they interact with each other: 4 | 5 | ```mermaid 6 | graph TB 7 | User((User)) 8 | ExternalSystems((External Systems)) 9 | 10 | subgraph "Binsider Architecture" 11 | CLI["Command Line Interface"] 12 | 13 | subgraph "Core Components" 14 | Analyzer["Analyzer"] 15 | ELFHandler["ELF Handler"] 16 | StringExtractor["String Extractor"] 17 | Tracer["Tracer"] 18 | HexdumpViewer["Hexdump Viewer"] 19 | end 20 | 21 | subgraph "TUI Components" 22 | TUIManager["TUI Manager
(Ratatui)"] 23 | EventHandler["Event Handler"] 24 | UIRenderer["UI Renderer
"] 25 | StateManager["State Manager"] 26 | end 27 | 28 | subgraph "Shared Components" 29 | ErrorHandler["Error Handler"] 30 | FileIO["File I/O"] 31 | ArgParser["Argument Parser
(clap)"] 32 | end 33 | end 34 | 35 | User --> CLI 36 | CLI --> Analyzer 37 | Analyzer --> ELFHandler 38 | Analyzer --> StringExtractor 39 | Analyzer --> Tracer 40 | Analyzer --> HexdumpViewer 41 | CLI --> TUIManager 42 | TUIManager --> EventHandler 43 | TUIManager --> UIRenderer 44 | TUIManager --> StateManager 45 | CLI --> ArgParser 46 | Analyzer --> FileIO 47 | Tracer --> ExternalSystems 48 | 49 | ErrorHandler -.-> CLI 50 | ErrorHandler -.-> Analyzer 51 | ErrorHandler -.-> TUIManager 52 | 53 | classDef core fill:#2694ab,stroke:#1a6d7d,color:#ffffff 54 | classDef tui fill:#1168bd,stroke:#0b4884,color:#ffffff 55 | classDef shared fill:#6b8e23,stroke:#556b2f,color:#ffffff 56 | classDef external fill:#999999,stroke:#666666,color:#ffffff 57 | 58 | class Analyzer,ELFHandler,StringExtractor,Tracer,HexdumpViewer core 59 | class TUIManager,EventHandler,UIRenderer,StateManager tui 60 | class ErrorHandler,FileIO,ArgParser shared 61 | class ExternalSystems external 62 | 63 | %% Dotted lines for optional connections 64 | linkStyle 13,14,15 stroke-dasharray: 5 5 65 | ``` 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [**binsider**](https://binsider.dev): Analyze ELF binaries like a boss 😼🕵️‍♂️ 4 | 5 | ## 0.2.1 - 2024-10-10 6 | 7 | ### 🚀 Features 8 | 9 | - *(tui)* Use stdout for rendering by @orhun in [#79](https://github.com/orhun/binsider/pull/79) 10 | - *(ui)* Support shift+tab for going to the previous tab by @XXMA16 in [#70](https://github.com/orhun/binsider/pull/70) 11 | - *(cli)* Add `--tab` argument by @josueBarretogit in [#60](https://github.com/orhun/binsider/pull/60) 12 | - *(general)* Display the number of shared libraries by @sumit0190 in [#58](https://github.com/orhun/binsider/pull/58) 13 | 14 | ### 🐛 Bug Fixes 15 | 16 | - *(tui)* [**breaking**] Query the terminal background once by @orhun in [#62](https://github.com/orhun/binsider/pull/62) 17 | - *(flake)* Add missing meta section to flake by @ch4og in [#74](https://github.com/orhun/binsider/pull/74) 18 | - *(cd)* Enable cross compilationby @orhun 19 | 20 | ### ⚡ Performance 21 | 22 | - *(flake)* Speed up rebuild by using naersk by @ch4og in [#76](https://github.com/orhun/binsider/pull/76) 23 | 24 | ### ⚙️ Miscellaneous Tasks 25 | 26 | - *(deny)* Update ignored advisoriesby @orhun 27 | - *(ci)* Add nix flake build by @ch4og in [#75](https://github.com/orhun/binsider/pull/75) 28 | 29 | ## New Contributors 30 | * @XXMA16 made their first contribution in [#70](https://github.com/orhun/binsider/pull/70) 31 | * @josueBarretogit made their first contribution in [#60](https://github.com/orhun/binsider/pull/60) 32 | * @sumit0190 made their first contribution in [#58](https://github.com/orhun/binsider/pull/58) 33 | 34 | **Full Changelog**: https://github.com/orhun/binsider/compare/v0.2.0...0.2.1 35 | 36 | ## 0.2.0 - 2024-09-30 37 | 38 | ✨ See the blog post about this release: 39 | 40 | ### 🚀 Features 41 | 42 | - *(ui)* Add loading/splash screen by @orhun in [#55](https://github.com/orhun/binsider/pull/55) 43 | - *(lib)* Add example/documentation about using as a library by @orhun in [#52](https://github.com/orhun/binsider/pull/52) 44 | - *(dynamic)* Support running binaries with CLI arguments by @orhun in [#49](https://github.com/orhun/binsider/pull/49) 45 | - *(static)* Reorder symbol table for better readability by @orhun in [#42](https://github.com/orhun/binsider/pull/42) 46 | - *(dynamic)* Make dynamic analysis optional for better platform support by @orhun in [#31](https://github.com/orhun/binsider/pull/31) 47 | - *(tui)* Improve the white theme support by @orhun in [#23](https://github.com/orhun/binsider/pull/23) 48 | - *(nix)* Add a simple flake.nix by @jla2000 in [#14](https://github.com/orhun/binsider/pull/14) 49 | 50 | ### 🐛 Bug Fixes 51 | 52 | - *(ui)* Avoid crashing when logo does not fit the terminal by @orhun 53 | - *(test)* Update file info arguments by @orhun 54 | - *(dynamic)* Fix locating the binary by @orhun in [#48](https://github.com/orhun/binsider/pull/48) 55 | - *(dynamic)* Sort the shared library list by @orhun in [#37](https://github.com/orhun/binsider/pull/37) 56 | - *(strings)* Replace unicode whitespace for correct rendering by @orhun in [#28](https://github.com/orhun/binsider/pull/28) 57 | - *(file)* Do not panic if creation time is not supported by @orhun in [#25](https://github.com/orhun/binsider/pull/25) 58 | - *(tui)* Stop the event handler on quit by @orhun in [#24](https://github.com/orhun/binsider/pull/24) 59 | - *(flake)* Fix test failure on Nix by @ch4og in [#30](https://github.com/orhun/binsider/pull/30) 60 | - *(docker)* Fix inconsistent keyword casing by @orhun 61 | - *(ci)* Only run library unit tests in CI by @orhun 62 | - *(test)* Ensure that binary is built before the test runs by @samueltardieu in [#11](https://github.com/orhun/binsider/pull/11) 63 | - *(website)* Handle GitHub release version correctly by @orhun 64 | 65 | ### 📚 Documentation 66 | 67 | - *(blog)* Add blog post for 0.2.0 release by @orhun in [#53](https://github.com/orhun/binsider/pull/53) 68 | 69 | ### ⚙️ Miscellaneous Tasks 70 | 71 | - *(website)* Add discord link by @orhun 72 | - *(changelog)* Update git-cliff config by @orhun 73 | - *(website)* Do not deploy website for pull requests by @orhun 74 | 75 | ## New Contributors 76 | 77 | * @ch4og made their first contribution in [#30](https://github.com/orhun/binsider/pull/30) 78 | * @samueltardieu made their first contribution in [#11](https://github.com/orhun/binsider/pull/11) 79 | * @jla2000 made their first contribution in [#14](https://github.com/orhun/binsider/pull/14) 80 | 81 | **Full Changelog**: https://github.com/orhun/binsider/compare/v0.1.0...v0.2.0 82 | 83 | ## 0.1.0 - 2024-09-11 84 | 85 | Initial release 🚀 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | binsiderdev@protonmail.com 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | First off, thank you for considering contributing to `binsider`! 4 | 5 | If your contribution is not straightforward, please first discuss the change you 6 | wish to make by creating a new issue before making the change. 7 | 8 | ## Reporting Issues 9 | 10 | Before reporting an issue on the 11 | [issue tracker](https://github.com/orhun/binsider/issues), 12 | please check that it has not already been reported by searching for some related 13 | keywords. 14 | 15 | ## Pull Requests 16 | 17 | Try to do one pull request per change. 18 | 19 | ## Commit Message Format 20 | 21 | This project adheres to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 22 | A specification for adding human and machine readable meaning to commit messages. 23 | 24 | ### Commit Message Header 25 | 26 | ``` 27 | (): 28 | │ │ │ 29 | │ │ └─ Summary in present tense. Not capitalized. No period at the end. 30 | │ │ 31 | │ └─ Commit Scope 32 | │ 33 | └─ Commit Type: feat|fix|build|ci|docs|perf|refactor|test|chore 34 | ``` 35 | 36 | #### Type 37 | 38 | | feat | Features | A new feature | 39 | | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------ | 40 | | fix | Bug Fixes | A bug fix | 41 | | docs | Documentation | Documentation only changes | 42 | | style | Styles | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) | 43 | | refactor | Code Refactoring | A code change that neither fixes a bug nor adds a feature | 44 | | perf | Performance Improvements | A code change that improves performance | 45 | | test | Tests | Adding missing tests or correcting existing tests | 46 | | build | Builds | Changes that affect the build system or external dependencies (example scopes: main, serde) | 47 | | ci | Continuous Integrations | Changes to our CI configuration files and scripts (example scopes: Github Actions) | 48 | | chore | Chores | Other changes that don't modify src or test files | 49 | | revert | Reverts | Reverts a previous commit | 50 | 51 | ## Developing 52 | 53 | ### Setup 54 | 55 | This is no different than other Rust projects. 56 | 57 | ```bash 58 | git clone https://github.com/orhun/binsider 59 | cd binsider 60 | cargo test 61 | ``` 62 | 63 | ### Useful Commands 64 | 65 | Run clippy: 66 | 67 | ```bash 68 | cargo clippy --all-targets --all-features --workspace 69 | ``` 70 | 71 | Run all tests: 72 | 73 | ```bash 74 | cargo test --all-features --workspace 75 | ``` 76 | 77 | Check to see if there are code formatting issues: 78 | 79 | ```bash 80 | cargo fmt --all -- --check 81 | ``` 82 | 83 | Format the code in the project: 84 | 85 | ```bash 86 | cargo fmt --all 87 | ``` 88 | 89 | ## License 90 | 91 | By contributing, you agree that your contributions will be licensed under [The MIT License](./LICENSE-MIT) or [Apache License 2.0](./LICENSE-APACHE). 92 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "binsider" 3 | version = "0.2.1" 4 | description = "Analyze ELF binaries like a boss" 5 | authors = ["Orhun Parmaksız "] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://binsider.dev" 9 | repository = "https://github.com/orhun/binsider" 10 | keywords = ["analyze", "binary", "tui", "elf"] 11 | categories = ["command-line-utilities"] 12 | edition = "2021" 13 | rust-version = "1.74.1" 14 | include = [ 15 | "src/**/*", 16 | "Cargo.*", 17 | "LICENSE*", 18 | "README.md", 19 | "CHANGELOG.md", 20 | ] 21 | 22 | [features] 23 | default = ["dynamic-analysis"] 24 | dynamic-analysis = ["lurk-cli"] 25 | 26 | [dependencies] 27 | ratatui = "0.28.1" 28 | clap = { version = "4.5.20", features = ["derive", "env", "wrap_help", "cargo"] } 29 | thiserror = "2.0.11" 30 | elf = "0.7.1" 31 | rust-strings = "0.6.0" 32 | heh = "0.6.1" 33 | tui-input = "0.11.1" 34 | tui-popup = "0.5.0" 35 | unicode-width = "0.2.0" 36 | textwrap = "0.16.1" 37 | lurk-cli = { version = "0.3.9", optional = true } 38 | nix = { version = "0.29.0", features = ["ptrace", "signal"] } 39 | ansi-to-tui = "6.0.0" 40 | console = "0.15.8" 41 | which = "7.0.1" 42 | better-panic = "0.3.0" 43 | tui-big-text = "0.6.0" 44 | chrono = "0.4.38" 45 | bytesize = "1.3.0" 46 | sysinfo = { version = "0.33.1", default-features = false, features = ["user"] } 47 | webbrowser = "1.0.2" 48 | lddtree = "0.3.5" 49 | itertools = "0.14.0" 50 | termbg = "0.6.2" 51 | 52 | [dev-dependencies] 53 | pretty_assertions = "1.4.1" 54 | 55 | [profile.dev] 56 | opt-level = 0 57 | debug = true 58 | panic = "abort" 59 | 60 | [profile.test] 61 | opt-level = 0 62 | debug = true 63 | 64 | [profile.release] 65 | opt-level = 3 66 | debug = false 67 | panic = "unwind" 68 | lto = true 69 | codegen-units = 1 70 | strip = true 71 | 72 | [profile.bench] 73 | opt-level = 3 74 | debug = false 75 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.81-slim-bullseye AS builder 2 | WORKDIR /src 3 | COPY Cargo.toml Cargo.toml 4 | COPY Cargo.lock Cargo.lock 5 | RUN mkdir src/ && echo "fn main() {println!(\"failed to build\")}" > src/main.rs 6 | RUN cargo build --release 7 | RUN rm -f target/release/deps/binsider* 8 | COPY . . 9 | RUN cargo build --locked --release 10 | RUN mkdir -p build-out/ 11 | RUN cp target/release/binsider build-out/ 12 | 13 | FROM debian:bullseye-slim AS runner 14 | WORKDIR /app 15 | COPY --from=builder /src/build-out/binsider . 16 | USER 1000:1000 17 | ENTRYPOINT ["./binsider"] 18 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Orhun Parmaksız 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 | 3 | 4 |
5 | 6 | 7 |
8 |
9 | "Swiss army knife for reverse engineers." 10 |
11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 |

29 | 30 |

31 | Documentation | 32 | Website 33 |

34 | 35 | 😼🕵️‍♂️ **Binsider** can perform static and dynamic analysis, inspect strings, examine linked libraries, and perform hexdumps, all within a user-friendly terminal user interface! 36 | 37 | ## Quickstart 38 | 39 | > [!TIP] 40 | > Watch the quickstart video to get a glimpse of what `binsider` can do: [https://youtu.be/InhVCQoc5ZE](https://youtu.be/InhVCQoc5ZE) 41 | 42 | Install `binsider` with `cargo`: 43 | 44 | ```bash 45 | cargo install binsider 46 | ``` 47 | 48 | > [!NOTE] 49 | > See the other [installation methods](https://binsider.dev/installation/other/) 📦 50 | 51 | After the installation, you are pretty much set! 💯 52 | 53 | Just dive into the binaries by running `binsider`: 54 | 55 | ```bash 56 | binsider 57 | ``` 58 | 59 | ![Demo](website/src/content/assets/quickstart.gif) 60 | 61 | ## Features 62 | 63 | > [!NOTE] 64 | > The detailed documentation is available at 📚 65 | 66 | ### General Analysis 67 | 68 | You can retrieve general binary file information, including file size, ownership, permissions, date, and linked shared libraries (similar to [`stat(1)`](https://www.man7.org/linux/man-pages/man1/stat.1.html) and [`ldd(1)`](https://www.man7.org/linux/man-pages/man1/ldd.1.html)). 69 | 70 | [![General analysis](website/src/assets/demo/binsider-general-analysis.gif)](https://binsider.dev/usage/general-analysis) 71 | 72 | ➡️ 73 | 74 | ### Static Analysis 75 | 76 | You can analyze the ELF layout (such as sections, segments, symbols, and relocations) and navigate through them to get an in-depth understanding of the binary. 77 | 78 | [![Static analysis](website/src/assets/demo/binsider-static-analysis.gif)](https://binsider.dev/usage/static-analysis) 79 | 80 | ➡️ 81 | 82 | ### Dynamic Analysis 83 | 84 | It is possible to execute the binary and trace the system calls, signals, and the program's execution flow similar to [`strace(1)`](https://man7.org/linux/man-pages/man1/strace.1.html) and [`ltrace(1)`](https://man7.org/linux/man-pages/man1/ltrace.1.html). 85 | 86 | [![Dynamic analysis](website/src/assets/demo/binsider-dynamic-analysis.gif)](https://binsider.dev/usage/dynamic-analysis) 87 | 88 | ➡️ 89 | 90 | ### String Extraction 91 | 92 | Similar to the [`strings(1)`](https://linux.die.net/man/1/strings) command, `binsider` is able to extract strings from the binary file with the purpose of discovering interesting strings such as URLs, passwords, and other sensitive information. 93 | 94 | [![String extraction](website/src/assets/demo/binsider-strings.gif)](https://binsider.dev/usage/strings) 95 | 96 | ➡️ 97 | 98 | ### Hexdump 99 | 100 | `binsider` provides a rich dashboard along with a hexdump view to analyze the binary content in a structured manner. 101 | 102 | [![Hexdump](website/src/assets/demo/binsider-hexdump.gif)](https://binsider.dev/usage/hexdump) 103 | 104 | ➡️ 105 | 106 | ## Acknowledgements 107 | 108 | Shoutout to [@harunocaksiz](https://github.com/harunocaksiz) for sticking with me during our military service in the summer of 2024 and creating the awesome **binsider** logo! (o7) 109 | 110 | ## Contributing 111 | 112 | See the [contribution guidelines](CONTRIBUTING.md). 113 | 114 | 115 | 116 | 117 | ## License 118 | 119 | Licensed under either of [Apache License Version 2.0](./LICENSE-APACHE) or [The MIT License](./LICENSE-MIT) at your option. 120 | 121 | 🦀 ノ( º \_ º ノ) - respect crables! 122 | 123 | ## Copyright 124 | 125 | Copyright © 2024, [Orhun Parmaksız](mailto:orhunparmaksiz@gmail.com) 126 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Creating a Release 2 | 3 | [GitHub](https://github.com/orhun/binsider/releases) and [crates.io](https://crates.io/crates/binsider/) releases are automated via [GitHub actions](.github/workflows/cd.yml) and triggered by pushing a tag. 4 | 5 | 1. Bump the version in [Cargo.toml](Cargo.toml) according to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 2. Update [Cargo.lock](Cargo.lock) by building the project: `cargo build` 7 | 3. Update [CHANGELOG.md](CHANGELOG.md) by running [`git-cliff`](https://git-cliff.org). 8 | 4. Commit your changes. 9 | 5. Create a new tag: `git tag -s -a v[X.Y.Z]` 10 | 6. Push the tag: `git push --tags` 11 | 7. Announce the release! 🥳 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following versions are supported with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.1.x | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please use the [GitHub Security Advisories](https://github.com/orhun/binsider/security/advisories/new) feature to report vulnerabilities. 14 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://github.com/orhun/git-cliff 2 | 3 | [remote.github] 4 | owner = "orhun" 5 | repo = "binsider" 6 | 7 | [changelog] 8 | # changelog header 9 | header = """ 10 | 11 | 12 | [**binsider**](https://binsider.dev): Analyze ELF binaries like a boss 😼🕵️‍♂️\n 13 | """ 14 | # template for the changelog body 15 | # https://keats.github.io/tera/docs/#introduction 16 | body = """ 17 | {% if version %}\ 18 | ## {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## unreleased 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | striptags | trim | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ 26 | {% if commit.breaking %}[**breaking**] {% endif %}\ 27 | {{ commit.message | upper_first }}\ 28 | {% if commit.remote.username %}by @{{ commit.remote.username }}{%- endif -%}\ 29 | {% if commit.remote.pr_number %} in \ 30 | [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) 31 | {%- endif %}\ 32 | {% endfor %} 33 | {% endfor %} 34 | 35 | {%- if github -%} 36 | {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} 37 | {% raw %}\n{% endraw -%} 38 | ## New Contributors 39 | {%- endif %}\ 40 | {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} 41 | * @{{ contributor.username }} made their first contribution 42 | {%- if contributor.pr_number %} in \ 43 | [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ 44 | {%- endif %} 45 | {%- endfor -%} 46 | {%- endif -%} 47 | 48 | {% if version %} 49 | {% if previous.version %} 50 | **Full Changelog**: {{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }} 51 | {% endif %} 52 | {% else -%} 53 | {% raw %}\n{% endraw %} 54 | {% endif %} 55 | 56 | {%- macro remote_url() -%} 57 | https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} 58 | {%- endmacro -%} 59 | """ 60 | # template for the changelog footer 61 | footer = """ 62 | 63 | """ 64 | # remove the leading and trailing s 65 | trim = true 66 | # postprocessors 67 | postprocessors = [ 68 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 69 | ] 70 | 71 | [git] 72 | # parse the commits based on https://www.conventionalcommits.org 73 | conventional_commits = true 74 | # filter out the commits that are not conventional 75 | filter_unconventional = true 76 | # regex for preprocessing the commit messages 77 | commit_preprocessors = [ 78 | # remove issue numbers from commits 79 | { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, 80 | ] 81 | # regex for parsing and grouping commits 82 | commit_parsers = [ 83 | { message = "^feat", group = "🚀 Features" }, 84 | { message = "^fix", group = "🐛 Bug Fixes" }, 85 | { message = "^doc", group = "📚 Documentation" }, 86 | { message = "^perf", group = "⚡ Performance" }, 87 | { message = "^refactor", group = "🚜 Refactor" }, 88 | { message = "^style", group = "🎨 Styling" }, 89 | { message = "^test", group = "🧪 Testing" }, 90 | { message = "^chore\\(release\\): prepare for", skip = true }, 91 | { message = "^chore\\(deps.*\\)", skip = true }, 92 | { message = "^chore\\(pr\\)", skip = true }, 93 | { message = "^chore\\(pull\\)", skip = true }, 94 | { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, 95 | { body = ".*security", group = "🛡️ Security" }, 96 | { message = "^revert", group = "◀️ Revert" }, 97 | ] 98 | # regex for matching git tags 99 | tag_pattern = "v[0-9].*" 100 | # regex for skipping tags 101 | skip_tags = "beta|alpha" 102 | # regex for ignoring tags 103 | ignore_tags = "rc" 104 | # sort the commits inside sections by oldest/newest order 105 | sort_commits = "newest" 106 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 50..75 3 | round: up 4 | precision: 2 5 | status: 6 | project: 7 | default: 8 | target: auto 9 | threshold: 5% 10 | branches: 11 | - master 12 | if_ci_failed: error 13 | patch: off 14 | -------------------------------------------------------------------------------- /committed.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://github.com/crate-ci/committed 2 | 3 | # https://www.conventionalcommits.org 4 | style = "conventional" 5 | # allow merge commits 6 | merge_commit = true 7 | # subject is not required to be capitalized 8 | subject_capitalized = false 9 | # subject should start with an imperative verb 10 | imperative_subject = true 11 | # subject should not end with a punctuation 12 | subject_not_punctuated = true 13 | # disable line length 14 | line_length = 0 15 | # disable subject length 16 | subject_length = 0 17 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://github.com/EmbarkStudios/cargo-deny 2 | 3 | [licenses] 4 | confidence-threshold = 0.8 5 | allow = [ 6 | "MIT", 7 | "Apache-2.0", 8 | "Unicode-DFS-2016", 9 | "BSD-2-Clause", 10 | "BSL-1.0", 11 | "Zlib", 12 | ] 13 | 14 | [sources] 15 | unknown-registry = "deny" 16 | unknown-git = "warn" 17 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 18 | 19 | [advisories] 20 | ignore = ["RUSTSEC-2023-0040", "RUSTSEC-2023-0059"] 21 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | //! A simple demo of how to use the binsider as a library. 2 | 3 | use std::{env, fs, path::PathBuf, sync::mpsc, time::Duration}; 4 | 5 | use binsider::{prelude::*, tui::ui::Tab}; 6 | 7 | use ratatui::{ 8 | crossterm::event::{self, Event as CrosstermEvent, KeyCode}, 9 | Frame, 10 | }; 11 | 12 | fn main() -> Result<()> { 13 | // Create an analyzer. 14 | let path = PathBuf::from(env::args().next().expect("no file given")); 15 | let file_data = fs::read(&path)?; 16 | let file_info = FileInfo::new( 17 | path.to_str().unwrap_or_default(), 18 | None, 19 | file_data.as_slice(), 20 | )?; 21 | let analyzer = Analyzer::new(file_info, 15, vec![])?; 22 | let mut state = State::new(analyzer, None)?; 23 | let (sender, receiver) = mpsc::channel(); 24 | state.analyzer.extract_strings(sender.clone()); 25 | 26 | let mut terminal = ratatui::init(); 27 | loop { 28 | // Render the UI. 29 | terminal.draw(|frame: &mut Frame| { 30 | binsider::tui::ui::render(&mut state, frame); 31 | })?; 32 | 33 | // Handle terminal events. 34 | if event::poll(Duration::from_millis(16))? { 35 | if let CrosstermEvent::Key(key) = event::read()? { 36 | if key.code == KeyCode::Char('q') { 37 | break; 38 | } 39 | let command = Command::from(key); 40 | state.run_command(command, sender.clone())?; 41 | } 42 | } 43 | 44 | // Handle binsider events. 45 | if let Ok(Event::FileStrings(strings)) = receiver.try_recv() { 46 | state.strings_loaded = true; 47 | state.analyzer.strings = Some(strings?.into_iter().map(|(v, l)| (l, v)).collect()); 48 | if state.tab == Tab::Strings { 49 | state.handle_tab()?; 50 | } 51 | } 52 | } 53 | ratatui::restore(); 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "naersk": { 22 | "inputs": { 23 | "nixpkgs": [ 24 | "nixpkgs" 25 | ] 26 | }, 27 | "locked": { 28 | "lastModified": 1733346208, 29 | "narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=", 30 | "owner": "nix-community", 31 | "repo": "naersk", 32 | "rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "nix-community", 37 | "repo": "naersk", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1733758725, 44 | "narHash": "sha256-2swefXrg+9Xl4LdOm0xRXfMgfYYPCDMJRU/fffe7aew=", 45 | "owner": "nixos", 46 | "repo": "nixpkgs", 47 | "rev": "f05e64a83f7bb9331de58bcbb9fe08be4306ad80", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "nixos", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "root": { 57 | "inputs": { 58 | "flake-utils": "flake-utils", 59 | "naersk": "naersk", 60 | "nixpkgs": "nixpkgs" 61 | } 62 | }, 63 | "systems": { 64 | "locked": { 65 | "lastModified": 1681028828, 66 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 67 | "owner": "nix-systems", 68 | "repo": "default", 69 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "nix-systems", 74 | "repo": "default", 75 | "type": "github" 76 | } 77 | } 78 | }, 79 | "root": "root", 80 | "version": 7 81 | } 82 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs?nixpkgs-unstable"; 4 | naersk = { 5 | url = "github:nix-community/naersk"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | flake-utils.url = "github:numtide/flake-utils"; 9 | }; 10 | 11 | outputs = { nixpkgs, flake-utils, naersk, ... }: 12 | flake-utils.lib.eachDefaultSystem (system: 13 | let 14 | pkgs = nixpkgs.legacyPackages.${system}; 15 | naersk' = pkgs.callPackage naersk { }; 16 | binsider' = { release }: naersk'.buildPackage { 17 | name = "binsider"; 18 | src = ./.; 19 | inherit release; 20 | meta = with pkgs.lib; { 21 | description = "Analyze ELF binaries like a boss"; 22 | homepage = "https://binsider.dev/"; 23 | license = [ licenses.mit licenses.asl20 ]; 24 | }; 25 | }; 26 | in 27 | rec { 28 | packages = rec { 29 | binsider = binsider' { release = true; }; 30 | binsider-debug = binsider' { release = false; }; 31 | default = binsider; 32 | }; 33 | checks.check = packages.binsider-debug; 34 | } 35 | ); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://rust-lang.github.io/rustfmt/ 2 | 3 | edition = "2021" 4 | max_width = 100 5 | use_field_init_shorthand = true 6 | use_try_shorthand = true 7 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | elf::Elf, 3 | error::{Error, Result}, 4 | file::FileInfo, 5 | tui::event::Event, 6 | }; 7 | use elf::{endian::AnyEndian, ElfBytes}; 8 | use heh::app::Application as Heh; 9 | use heh::decoder::Encoding; 10 | use lddtree::DependencyAnalyzer; 11 | use ratatui::text::Line; 12 | use rust_strings::BytesConfig; 13 | use std::{ 14 | fmt::{self, Debug, Formatter}, 15 | path::PathBuf, 16 | sync::mpsc, 17 | thread, 18 | }; 19 | 20 | /// Tracer data. 21 | #[derive(Debug, Default)] 22 | pub struct TraceData { 23 | /// System calls. 24 | pub syscalls: Vec, 25 | /// Summary. 26 | pub summary: Vec, 27 | } 28 | 29 | /// Binary analyzer. 30 | pub struct Analyzer<'a> { 31 | /// List of files that are being analyzed. 32 | pub files: Vec, 33 | /// Current file information. 34 | pub file: FileInfo<'a>, 35 | /// Elf properties. 36 | pub elf: Elf, 37 | /// Strings. 38 | pub strings: Option>, 39 | /// Min length of the strings. 40 | pub strings_len: usize, 41 | /// Heh application. 42 | pub heh: Heh, 43 | /// Tracer data. 44 | pub tracer: TraceData, 45 | /// System calls. 46 | pub system_calls: Vec>, 47 | /// Library dependencies. 48 | pub dependencies: Vec<(String, String)>, 49 | } 50 | 51 | impl Debug for Analyzer<'_> { 52 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 53 | f.debug_struct("Analyzer") 54 | .field("bytes", &self.file.bytes) 55 | .finish() 56 | } 57 | } 58 | 59 | impl<'a> Analyzer<'a> { 60 | /// Constructs a new instance. 61 | pub fn new( 62 | mut file_info: FileInfo<'a>, 63 | strings_len: usize, 64 | files: Vec, 65 | ) -> Result { 66 | let elf_bytes = ElfBytes::::minimal_parse(file_info.bytes)?; 67 | let elf = Elf::try_from(elf_bytes)?; 68 | let heh = Heh::new(file_info.open_file()?, Encoding::Ascii, 0) 69 | .map_err(|e| Error::HexdumpError(e.to_string()))?; 70 | Ok(Self { 71 | dependencies: Self::extract_libs(&file_info)?, 72 | files, 73 | file: file_info, 74 | elf, 75 | strings: None, 76 | strings_len, 77 | heh, 78 | tracer: TraceData::default(), 79 | system_calls: Vec::new(), 80 | }) 81 | } 82 | 83 | /// Extracts the library dependencies. 84 | pub fn extract_libs(file_info: &FileInfo<'a>) -> Result> { 85 | let mut dependencies = DependencyAnalyzer::default() 86 | .analyze(file_info.path)? 87 | .libraries 88 | .clone() 89 | .into_iter() 90 | .map(|(name, lib)| { 91 | ( 92 | name.to_string(), 93 | lib.realpath 94 | .unwrap_or(lib.path) 95 | .to_string_lossy() 96 | .to_string(), 97 | ) 98 | }) 99 | .collect::>(); 100 | dependencies.sort_by(|a, b| { 101 | let lib_condition1 = a.0.starts_with("lib"); 102 | let lib_condition2 = b.0.starts_with("lib"); 103 | match (lib_condition1, lib_condition2) { 104 | (true, false) => std::cmp::Ordering::Less, 105 | (false, true) => std::cmp::Ordering::Greater, 106 | _ => a.0.cmp(&b.0), 107 | } 108 | }); 109 | Ok(dependencies) 110 | } 111 | 112 | /// Returns the sequences of printable characters. 113 | pub fn extract_strings(&mut self, event_sender: mpsc::Sender) { 114 | let config = BytesConfig::new(self.file.bytes.to_vec()).with_min_length(self.strings_len); 115 | thread::spawn(move || { 116 | event_sender 117 | .send(Event::FileStrings( 118 | rust_strings::strings(&config).map_err(|e| Error::StringsError(e.to_string())), 119 | )) 120 | .expect("failed to send strings event"); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ratatui::style::Color; 3 | use std::path::PathBuf; 4 | 5 | use crate::tui::ui::Tab; 6 | 7 | /// Argument parser powered by [`clap`]. 8 | #[derive(Clone, Debug, Default, Parser)] 9 | #[clap( 10 | version, 11 | author = clap::crate_authors!("\n"), 12 | about, 13 | rename_all_env = "screaming-snake", 14 | help_template = "\ 15 | {before-help}{name} {version} 16 | {author-with-newline}{about-with-newline} 17 | {usage-heading} 18 | {usage} 19 | 20 | {all-args}{after-help} 21 | ", 22 | )] 23 | pub struct Args { 24 | /// Binary / ELF object file. 25 | #[arg(env, name = "FILE")] 26 | pub files: Vec, 27 | 28 | /// Minimum length of strings. 29 | #[arg(env, short = 'n', long = "min-len", default_value = "15")] 30 | pub min_strings_len: usize, 31 | 32 | /// The initial application tab to open. 33 | #[arg(env, short = 't', long = "tab", default_value = "general")] 34 | pub tab: Tab, 35 | 36 | /// Accent color of the application. 37 | #[arg(env, long, value_name = "COLOR")] 38 | pub accent_color: Option, 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use clap::CommandFactory; 45 | #[test] 46 | fn test_args() { 47 | Args::command().debug_assert(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/elf/dynamic.rs: -------------------------------------------------------------------------------- 1 | use crate::elf::Property; 2 | use elf::{dynamic::Dyn, endian::AnyEndian, parse::ParsingTable, ParseError}; 3 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 4 | 5 | /// ELF dynamic section wrapper. 6 | #[derive(Clone, Debug, Default)] 7 | pub struct Dynamic { 8 | /// Dynamics. 9 | dynamics: Vec, 10 | } 11 | 12 | impl<'a> TryFrom>> for Dynamic { 13 | type Error = ParseError; 14 | fn try_from(value: Option>) -> Result { 15 | let parsing_table = value.ok_or_else(|| { 16 | ParseError::IOError(IoError::new( 17 | IoErrorKind::Other, 18 | "parsing table does not exist", 19 | )) 20 | })?; 21 | Ok(Self { 22 | dynamics: parsing_table.iter().collect(), 23 | }) 24 | } 25 | } 26 | 27 | impl Property<'_> for Dynamic { 28 | fn items(&self) -> Vec> { 29 | self.dynamics 30 | .iter() 31 | .map(|dynamic| { 32 | let d_tag_str = elf::to_str::d_tag_to_str(dynamic.d_tag) 33 | .map_or(format!("{:#X?}", dynamic.d_tag), |val| val.to_string()); 34 | vec![ 35 | d_tag_str.to_string().trim_start_matches("DT_").to_string(), 36 | format!("{:#X?}", dynamic.clone().d_val()), 37 | ] 38 | }) 39 | .collect() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/elf/header.rs: -------------------------------------------------------------------------------- 1 | use crate::elf::Property; 2 | use bytesize::ByteSize; 3 | use elf::parse::ParsingTable; 4 | use elf::section::SectionHeader; 5 | use elf::segment::ProgramHeader; 6 | use elf::string_table::StringTable; 7 | use elf::{endian::AnyEndian, file::FileHeader as ElfFileHeader}; 8 | use elf::{to_str::*, ParseError}; 9 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 10 | 11 | /// ELF file header wrapper. 12 | #[derive(Clone, Copy, Debug)] 13 | pub struct FileHeaders { 14 | /// Inner type. 15 | inner: ElfFileHeader, 16 | } 17 | 18 | impl From> for FileHeaders { 19 | fn from(inner: ElfFileHeader) -> Self { 20 | Self { inner } 21 | } 22 | } 23 | 24 | impl Property<'_> for FileHeaders { 25 | fn items(&self) -> Vec> { 26 | let mut headers = Vec::new(); 27 | headers.push(vec![ 28 | String::from("Class"), 29 | format!("{:?}", self.inner.class), 30 | ]); 31 | headers.push(vec![ 32 | String::from("Endianness"), 33 | format!("{:?}", self.inner.endianness), 34 | ]); 35 | headers.push(vec![ 36 | String::from("Version"), 37 | match self.inner.version { 38 | 1 => String::from("1 (current)"), 39 | v => v.to_string(), 40 | }, 41 | ]); 42 | headers.push(vec![ 43 | String::from("OS/ABI"), 44 | e_osabi_to_string(self.inner.osabi) 45 | .trim_start_matches("ELFOSABI_") 46 | .to_string(), 47 | ]); 48 | headers.push(vec![ 49 | String::from("ABI Version"), 50 | self.inner.abiversion.to_string(), 51 | ]); 52 | headers.push(vec![ 53 | String::from("Type"), 54 | match e_type_to_human_str(self.inner.e_type) { 55 | Some(s) => s.to_string(), 56 | None => format!("e_type({:#x})", self.inner.e_type), 57 | }, 58 | ]); 59 | headers.push(vec![ 60 | String::from("Arch"), 61 | match e_machine_to_human_str(self.inner.e_machine) { 62 | Some(s) => s.to_string(), 63 | None => format!("e_machine({:#x})", self.inner.e_machine), 64 | }, 65 | ]); 66 | headers.push(vec![ 67 | String::from("Entry point address"), 68 | format!("{:#x}", self.inner.e_entry), 69 | ]); 70 | headers.push(vec![ 71 | String::from("Start of program headers"), 72 | format!("{} (bytes into file)", self.inner.e_phoff), 73 | ]); 74 | headers.push(vec![ 75 | String::from("Start of section headers"), 76 | format!("{} (bytes into file)", self.inner.e_shoff), 77 | ]); 78 | headers.push(vec![ 79 | String::from("Flags"), 80 | format!("{:#x}", self.inner.e_flags), 81 | ]); 82 | headers.push(vec![ 83 | String::from("Size of this header"), 84 | format!("{} (bytes)", self.inner.e_ehsize), 85 | ]); 86 | headers.push(vec![ 87 | String::from("Size of program header"), 88 | format!("{} (bytes)", self.inner.e_phentsize), 89 | ]); 90 | headers.push(vec![ 91 | String::from("Number of program headers"), 92 | self.inner.e_phnum.to_string(), 93 | ]); 94 | headers.push(vec![ 95 | String::from("Size of section headers"), 96 | format!("{} (bytes)", self.inner.e_shentsize), 97 | ]); 98 | headers.push(vec![ 99 | String::from("Number of section headers"), 100 | self.inner.e_shnum.to_string(), 101 | ]); 102 | headers.push(vec![ 103 | String::from("Section headers string table section index"), 104 | self.inner.e_shstrndx.to_string(), 105 | ]); 106 | headers 107 | } 108 | } 109 | 110 | /// ELF program header wrapper. 111 | #[derive(Clone, Debug)] 112 | pub struct ProgramHeaders { 113 | /// Inner type. 114 | inner: Vec, 115 | /// Human readable format 116 | human_readable: bool, 117 | } 118 | 119 | impl ProgramHeaders { 120 | /// Toggles the value for human readable format. 121 | pub fn toggle_readability(&mut self) { 122 | self.human_readable = !self.human_readable; 123 | } 124 | } 125 | 126 | impl From> for ProgramHeaders { 127 | fn from(inner: Vec) -> Self { 128 | Self { 129 | inner, 130 | human_readable: true, 131 | } 132 | } 133 | } 134 | 135 | impl Property<'_> for ProgramHeaders { 136 | fn items(&self) -> Vec> { 137 | self.inner 138 | .iter() 139 | .map(|item| { 140 | vec![ 141 | elf::to_str::p_type_to_string(item.p_type) 142 | .trim_start_matches("PT_") 143 | .to_string(), 144 | format!("{:#x}", item.p_offset), 145 | format!("{:#x}", item.p_vaddr), 146 | format!("{:#x}", item.p_paddr), 147 | if self.human_readable { 148 | format!("{}", ByteSize(item.p_filesz)) 149 | } else { 150 | format!("{:#x}", item.p_filesz) 151 | }, 152 | if self.human_readable { 153 | format!("{}", ByteSize(item.p_memsz)) 154 | } else { 155 | format!("{:#x}", item.p_memsz) 156 | }, 157 | elf::to_str::p_flags_to_string(item.p_flags), 158 | format!("{:#x}", item.p_align), 159 | ] 160 | }) 161 | .collect() 162 | } 163 | } 164 | 165 | /// ELF file section header wrapper. 166 | #[derive(Clone, Debug, Default)] 167 | pub struct SectionHeaders { 168 | /// Inner type. 169 | inner: Vec, 170 | /// Section names. 171 | names: Vec, 172 | /// Human readable format 173 | human_readable: bool, 174 | } 175 | 176 | impl SectionHeaders { 177 | /// Toggles the value for human readable format. 178 | pub fn toggle_readability(&mut self) { 179 | self.human_readable = !self.human_readable; 180 | } 181 | } 182 | 183 | impl<'a> 184 | TryFrom<( 185 | Option>, 186 | Option>, 187 | )> for SectionHeaders 188 | { 189 | type Error = ParseError; 190 | fn try_from( 191 | value: ( 192 | Option>, 193 | Option>, 194 | ), 195 | ) -> Result { 196 | let (parsing_table, string_table) = ( 197 | value.0.ok_or_else(|| { 198 | ParseError::IOError(IoError::new( 199 | IoErrorKind::Other, 200 | "parsing table does not exist", 201 | )) 202 | })?, 203 | value.1.ok_or_else(|| { 204 | ParseError::IOError(IoError::new( 205 | IoErrorKind::Other, 206 | "string table does not exist", 207 | )) 208 | })?, 209 | ); 210 | Ok(Self { 211 | inner: parsing_table.iter().collect(), 212 | names: parsing_table 213 | .iter() 214 | .map(|v| { 215 | string_table 216 | .get(v.sh_name as usize) 217 | .map(|v| v.to_string()) 218 | .unwrap_or_else(|_| String::from("unknown")) 219 | }) 220 | .collect(), 221 | human_readable: true, 222 | }) 223 | } 224 | } 225 | 226 | impl Property<'_> for SectionHeaders { 227 | fn items(&self) -> Vec> { 228 | self.inner 229 | .iter() 230 | .enumerate() 231 | .map(|(i, header)| { 232 | vec![ 233 | self.names[i].to_string(), 234 | elf::to_str::sh_type_to_string(header.sh_type) 235 | .trim_start_matches("SHT_") 236 | .to_string(), 237 | format!("{:#x}", header.sh_addr), 238 | format!("{:#x}", header.sh_offset), 239 | if self.human_readable { 240 | format!("{}", ByteSize(header.sh_size)) 241 | } else { 242 | format!("{:#x}", header.sh_size) 243 | }, 244 | header.sh_entsize.to_string(), 245 | format!("{:#x}", header.sh_flags), 246 | header.sh_link.to_string(), 247 | header.sh_info.to_string(), 248 | header.sh_addralign.to_string(), 249 | ] 250 | }) 251 | .collect() 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/elf/mod.rs: -------------------------------------------------------------------------------- 1 | /// ELF dynamic section. 2 | pub mod dynamic; 3 | /// ELF header. 4 | pub mod header; 5 | /// ELF notes. 6 | pub mod notes; 7 | /// ELF relocations. 8 | pub mod relocations; 9 | /// ELF symbols. 10 | pub mod symbols; 11 | 12 | use dynamic::Dynamic; 13 | use elf::{endian::AnyEndian, ElfBytes, ParseError}; 14 | use header::{FileHeaders, ProgramHeaders, SectionHeaders}; 15 | use notes::Notes; 16 | use relocations::Relocations; 17 | use symbols::{DynamicSymbols, Symbols}; 18 | 19 | /// ELF property for receiving information. 20 | pub trait Property<'a> { 21 | /// Returns the items. 22 | fn items(&self) -> Vec>; 23 | } 24 | 25 | /// ELF information. 26 | #[derive(Debug)] 27 | pub enum Info { 28 | /// File headers. 29 | FileHeaders, 30 | /// Program headers (segments). 31 | ProgramHeaders, 32 | /// Section headers. 33 | SectionHeaders, 34 | /// Symbols. 35 | Symbols, 36 | /// Dynamic symbols. 37 | DynamicSymbols, 38 | /// Dynamics. 39 | Dynamics, 40 | /// Relocations. 41 | Relocations, 42 | /// Notes. 43 | Notes, 44 | } 45 | 46 | impl Info { 47 | /// Returns the title. 48 | pub fn title(&self) -> &str { 49 | match self { 50 | Info::FileHeaders => todo!(), 51 | Info::ProgramHeaders => "Program Headers / Segments", 52 | Info::SectionHeaders => "Section Headers", 53 | Info::Symbols => "Symbols", 54 | Info::DynamicSymbols => "Dynamic Symbols", 55 | Info::Dynamics => "Dynamic", 56 | Info::Relocations => "Relocations", 57 | Info::Notes => todo!(), 58 | } 59 | } 60 | 61 | /// Returns the headers. 62 | pub fn headers(&self) -> &[&str] { 63 | match self { 64 | Info::FileHeaders => todo!(), 65 | Info::ProgramHeaders => &[ 66 | "Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flags", "Align", 67 | ], 68 | Info::SectionHeaders => &[ 69 | "Name", "Type", "Addr", "Offset", "Size", "EntSiz", "Flags", "Link", "Info", 70 | "Align", 71 | ], 72 | Info::Symbols => &["Name", "Type", "Value", "Siz", "Bind", "Vis", "Ndx"], 73 | Info::DynamicSymbols => &["Name", "Reqs", "Type", "Value", "Siz", "Bind", "Vis", "Ndx"], 74 | Info::Dynamics => &["Tag", "Value"], 75 | Info::Relocations => &["Type", "Symbol", "Offset", "Addend"], 76 | Info::Notes => todo!(), 77 | } 78 | } 79 | } 80 | 81 | /// ELF wrapper. 82 | #[derive(Debug)] 83 | pub struct Elf { 84 | /// File headers. 85 | pub file_headers: FileHeaders, 86 | /// Program headers. 87 | pub program_headers: ProgramHeaders, 88 | /// Section headers. 89 | pub section_headers: SectionHeaders, 90 | /// Symbols. 91 | pub symbols: Symbols, 92 | /// Dynamic symbols. 93 | pub dynamic_symbols: DynamicSymbols, 94 | /// Dynamic. 95 | pub dynamic: Dynamic, 96 | /// Relocations. 97 | pub relocations: Relocations, 98 | /// Notes. 99 | pub notes: Notes, 100 | } 101 | 102 | impl<'a> TryFrom> for Elf { 103 | type Error = ParseError; 104 | fn try_from(elf_bytes: ElfBytes<'a, AnyEndian>) -> Result { 105 | Ok(Self { 106 | file_headers: FileHeaders::from(elf_bytes.ehdr), 107 | program_headers: ProgramHeaders::from(match elf_bytes.segments() { 108 | Some(segments) => segments.iter().collect(), 109 | None => vec![], 110 | }), 111 | section_headers: SectionHeaders::try_from(elf_bytes.section_headers_with_strtab()?) 112 | .unwrap_or_default(), 113 | symbols: Symbols::try_from(elf_bytes.symbol_table()?).unwrap_or_default(), 114 | dynamic_symbols: DynamicSymbols::try_from(( 115 | elf_bytes.dynamic_symbol_table()?, 116 | elf_bytes.symbol_version_table()?, 117 | )) 118 | .unwrap_or_default(), 119 | dynamic: Dynamic::try_from(elf_bytes.dynamic()?).unwrap_or_default(), 120 | relocations: Relocations::try_from(&elf_bytes).unwrap_or_default(), 121 | notes: Notes::try_from(&elf_bytes).unwrap_or_default(), 122 | }) 123 | } 124 | } 125 | 126 | impl Elf { 127 | /// Returns the information about the ELF file. 128 | pub fn info<'a>(&self, info: &Info) -> Box> { 129 | match info { 130 | Info::FileHeaders => Box::new(self.file_headers), 131 | Info::ProgramHeaders => Box::new(self.program_headers.clone()), 132 | Info::SectionHeaders => Box::new(self.section_headers.clone()), 133 | Info::Symbols => Box::new(self.symbols.clone()), 134 | Info::DynamicSymbols => Box::new(self.dynamic_symbols.clone()), 135 | Info::Dynamics => Box::new(self.dynamic.clone()), 136 | Info::Relocations => Box::new(self.relocations.clone()), 137 | Info::Notes => todo!(), 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/elf/notes.rs: -------------------------------------------------------------------------------- 1 | use elf::{endian::AnyEndian, note::Note as ElfNote, ElfBytes, ParseError}; 2 | use std::fmt::Write; 3 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 4 | 5 | /// Representation of an ELF note. 6 | #[derive(Clone, Debug, Default)] 7 | pub struct Note { 8 | /// Name of the note section. 9 | pub name: String, 10 | /// Header of the notes. 11 | pub header: Vec, 12 | /// Contents of the note. 13 | pub text: Vec, 14 | } 15 | 16 | /// ELF notes wrapper. 17 | #[derive(Clone, Debug, Default)] 18 | pub struct Notes { 19 | /// Notes text. 20 | pub inner: Vec, 21 | } 22 | 23 | impl<'a> TryFrom<&'a ElfBytes<'a, AnyEndian>> for Notes { 24 | type Error = ParseError; 25 | fn try_from(elf: &'a ElfBytes<'a, AnyEndian>) -> Result { 26 | let section_headers = elf.section_headers_with_strtab()?; 27 | let (parsing_table, string_table) = ( 28 | section_headers.0.ok_or_else(|| { 29 | ParseError::IOError(IoError::new( 30 | IoErrorKind::Other, 31 | "parsing table does not exist", 32 | )) 33 | })?, 34 | section_headers.1.ok_or_else(|| { 35 | ParseError::IOError(IoError::new( 36 | IoErrorKind::Other, 37 | "string table does not exist", 38 | )) 39 | })?, 40 | ); 41 | let mut notes = Vec::new(); 42 | parsing_table 43 | .iter() 44 | .filter(|v| v.sh_type == elf::abi::SHT_NOTE) 45 | .for_each(|section_header| { 46 | let name = string_table 47 | .get(section_header.sh_name as usize) 48 | .expect("failed to parse section name"); 49 | let elf_notes = elf 50 | .section_data_as_notes(§ion_header) 51 | .expect("failed to read notes section"); 52 | let mut note = Note { 53 | name: name.to_string(), 54 | ..Default::default() 55 | }; 56 | for elf_note in elf_notes { 57 | match elf_note { 58 | ElfNote::GnuAbiTag(abi) => { 59 | let os_str = elf::to_str::note_abi_tag_os_to_str(abi.os) 60 | .map_or(format!("{}", abi.os), |val| val.to_string()); 61 | note.header 62 | .extend(vec![String::from("OS"), String::from("ABI")]); 63 | note.text.extend(vec![ 64 | os_str, 65 | format!("{}.{}.{}", abi.major, abi.minor, abi.subminor), 66 | ]) 67 | } 68 | ElfNote::GnuBuildId(build_id) => { 69 | note.header.extend(vec![String::from("Build ID")]); 70 | note.text.extend(vec![build_id.0.iter().fold( 71 | String::new(), 72 | |mut output, b| { 73 | let _ = write!(output, "{b:02X}"); 74 | output 75 | }, 76 | )]); 77 | } 78 | ElfNote::Unknown(any) => { 79 | note.header.extend(vec![ 80 | String::from("Type"), 81 | String::from("Name"), 82 | String::from("Description"), 83 | ]); 84 | note.text.extend(vec![ 85 | any.n_type.to_string(), 86 | any.name.to_string(), 87 | format!("{:02X?}", any.desc), 88 | ]); 89 | } 90 | } 91 | } 92 | notes.push(note); 93 | }); 94 | Ok(Self { inner: notes }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/elf/relocations.rs: -------------------------------------------------------------------------------- 1 | use crate::elf::Property; 2 | use elf::{ 3 | endian::AnyEndian, 4 | relocation::{Rel, Rela}, 5 | ElfBytes, ParseError, 6 | }; 7 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 8 | 9 | /// ELF relocations wrapper. 10 | #[derive(Clone, Debug, Default)] 11 | pub struct Relocations { 12 | /// Relocations. 13 | rels: Vec, 14 | /// Relocations with addend. 15 | relas: Vec, 16 | } 17 | 18 | impl<'a> TryFrom<&'a ElfBytes<'a, AnyEndian>> for Relocations { 19 | type Error = ParseError; 20 | fn try_from(elf: &'a ElfBytes<'a, AnyEndian>) -> Result { 21 | let parsing_table = elf.section_headers().ok_or_else(|| { 22 | ParseError::IOError(IoError::new( 23 | IoErrorKind::Other, 24 | "parsing table does not exist", 25 | )) 26 | })?; 27 | Ok(Self { 28 | rels: parsing_table 29 | .iter() 30 | .filter(|shdr| shdr.sh_type == elf::abi::SHT_REL) 31 | .flat_map(|v| { 32 | elf.section_data_as_rels(&v) 33 | .expect("failed to read rels section") 34 | .collect::>() 35 | }) 36 | .collect(), 37 | relas: parsing_table 38 | .iter() 39 | .filter(|shdr| shdr.sh_type == elf::abi::SHT_RELA) 40 | .flat_map(|v| { 41 | elf.section_data_as_relas(&v) 42 | .expect("failed to read relas section") 43 | .collect::>() 44 | }) 45 | .collect(), 46 | }) 47 | } 48 | } 49 | 50 | impl Property<'_> for Relocations { 51 | fn items(&self) -> Vec> { 52 | let mut relocations = Vec::new(); 53 | self.rels.iter().for_each(|v| { 54 | relocations.push(vec![ 55 | format!("{:#X?}", v.r_type), 56 | format!("{:#X?}", v.r_sym), 57 | format!("{:#X?}", v.r_offset), 58 | String::from("-"), 59 | ]); 60 | }); 61 | self.relas.iter().for_each(|v| { 62 | relocations.push(vec![ 63 | format!("{:#X?}", v.r_type), 64 | format!("{:#X?}", v.r_sym), 65 | format!("{:#X?}", v.r_offset), 66 | format!("{:#X?}", v.r_addend), 67 | ]); 68 | }); 69 | relocations 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/elf/symbols.rs: -------------------------------------------------------------------------------- 1 | use crate::elf::Property; 2 | use elf::{ 3 | endian::AnyEndian, gnu_symver::SymbolVersionTable, parse::ParsingTable, 4 | string_table::StringTable, symbol::Symbol, ParseError, 5 | }; 6 | use std::io::{Error as IoError, ErrorKind as IoErrorKind}; 7 | 8 | /// ELF symbols wrapper. 9 | #[derive(Clone, Debug, Default)] 10 | pub struct Symbols { 11 | /// Symbols. 12 | symbols: Vec, 13 | /// Symbol names. 14 | names: Vec, 15 | } 16 | 17 | impl<'a> TryFrom, StringTable<'a>)>> for Symbols { 18 | type Error = ParseError; 19 | fn try_from( 20 | value: Option<(ParsingTable<'a, AnyEndian, Symbol>, StringTable<'a>)>, 21 | ) -> Result { 22 | let (parsing_table, string_table) = value.ok_or_else(|| { 23 | ParseError::IOError(IoError::new( 24 | IoErrorKind::Other, 25 | "symbol table does not exist", 26 | )) 27 | })?; 28 | Ok(Self { 29 | symbols: parsing_table.iter().collect(), 30 | names: parsing_table 31 | .iter() 32 | .map(|v| { 33 | string_table 34 | .get(v.st_name as usize) 35 | .map(|v| v.to_string()) 36 | .unwrap_or_else(|_| String::from("unknown")) 37 | }) 38 | .collect(), 39 | }) 40 | } 41 | } 42 | 43 | impl Property<'_> for Symbols { 44 | fn items(&self) -> Vec> { 45 | self.symbols 46 | .iter() 47 | .enumerate() 48 | .map(|(i, symbol)| { 49 | let name = self.names[i].to_string(); 50 | vec![ 51 | name, 52 | elf::to_str::st_symtype_to_string(symbol.st_symtype()) 53 | .trim_start_matches("STT_") 54 | .to_string(), 55 | format!("{:#x}", symbol.st_value), 56 | symbol.st_size.to_string(), 57 | elf::to_str::st_bind_to_string(symbol.st_bind()) 58 | .trim_start_matches("STB_") 59 | .to_string(), 60 | elf::to_str::st_vis_to_string(symbol.st_vis()) 61 | .trim_start_matches("STV_") 62 | .to_string(), 63 | symbol.st_shndx.to_string(), 64 | ] 65 | }) 66 | .collect() 67 | } 68 | } 69 | 70 | /// ELF dynamic symbols wrapper. 71 | #[derive(Clone, Debug, Default)] 72 | pub struct DynamicSymbols { 73 | /// Symbols. 74 | symbols: Vec, 75 | /// Names. 76 | names: Vec, 77 | /// Requirements. 78 | requirements: Vec, 79 | } 80 | 81 | impl<'a> 82 | TryFrom<( 83 | Option<(ParsingTable<'a, AnyEndian, Symbol>, StringTable<'a>)>, 84 | Option>, 85 | )> for DynamicSymbols 86 | { 87 | type Error = ParseError; 88 | fn try_from( 89 | value: ( 90 | Option<(ParsingTable<'a, AnyEndian, Symbol>, StringTable<'a>)>, 91 | Option>, 92 | ), 93 | ) -> Result { 94 | let (parsing_table, string_table) = value.0.ok_or_else(|| { 95 | ParseError::IOError(IoError::new( 96 | IoErrorKind::Other, 97 | "symbol table does not exist", 98 | )) 99 | })?; 100 | let version_table = value.1.ok_or_else(|| { 101 | ParseError::IOError(IoError::new( 102 | IoErrorKind::Other, 103 | "symbol version table does not exist", 104 | )) 105 | })?; 106 | Ok(Self { 107 | symbols: parsing_table.iter().collect(), 108 | names: parsing_table 109 | .iter() 110 | .map(|v| { 111 | string_table 112 | .get(v.st_name as usize) 113 | .map(|v| v.to_string()) 114 | .unwrap_or_else(|_| String::from("unknown")) 115 | }) 116 | .collect(), 117 | requirements: parsing_table 118 | .iter() 119 | .enumerate() 120 | .map(|(i, v)| { 121 | if v.is_undefined() { 122 | version_table 123 | .get_requirement(i) 124 | .ok() 125 | .flatten() 126 | .map(|v| v.name) 127 | .unwrap_or_else(|| "unknown") 128 | } else { 129 | "-" 130 | } 131 | .to_string() 132 | }) 133 | .collect(), 134 | }) 135 | } 136 | } 137 | 138 | impl Property<'_> for DynamicSymbols { 139 | fn items(&self) -> Vec> { 140 | self.symbols 141 | .iter() 142 | .enumerate() 143 | .map(|(i, symbol)| { 144 | vec![ 145 | self.names[i].to_string(), 146 | self.requirements[i].to_string(), 147 | elf::to_str::st_symtype_to_string(symbol.st_symtype()) 148 | .trim_start_matches("STT_") 149 | .to_string(), 150 | format!("{:#x}", symbol.st_value), 151 | symbol.st_size.to_string(), 152 | elf::to_str::st_bind_to_string(symbol.st_bind()) 153 | .trim_start_matches("STB_") 154 | .to_string(), 155 | elf::to_str::st_vis_to_string(symbol.st_vis()) 156 | .trim_start_matches("STV_") 157 | .to_string(), 158 | symbol.st_shndx.to_string(), 159 | ] 160 | }) 161 | .collect() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error as ThisError; 2 | 3 | /// Custom error type. 4 | #[derive(Debug, ThisError)] 5 | pub enum Error { 6 | /// Error that may occur during I/O operations. 7 | #[error("IO error: `{0}`")] 8 | IoError(#[from] std::io::Error), 9 | /// Error that may occur during locating binaries. 10 | #[error("Unable to locate binary: `{0}`")] 11 | WhichError(#[from] which::Error), 12 | /// Error that may occur while receiving messages from the channel. 13 | #[error("Channel receive error: `{0}`")] 14 | ChannelReceiveError(#[from] std::sync::mpsc::RecvError), 15 | /// Error that may occur while sending messages to the channel. 16 | #[error("Channel send error: `{0}`")] 17 | ChannelSendError(String), 18 | /// Error that may occur while parsing ELF files. 19 | #[error("ELF parse error: `{0}`")] 20 | ElfError(#[from] elf::parse::ParseError), 21 | /// Error that may occur while extracting strings from binary data. 22 | #[error("String extraction error: `{0}`")] 23 | StringsError(String), 24 | /// Error that may occur while running hexdump. 25 | #[error("Hexdump (heh) error: `{0}`")] 26 | HexdumpError(String), 27 | /// Error that may occur while tracing system calls. 28 | #[error("Tracing system call error: `{0}`")] 29 | TraceError(String), 30 | /// Error that may occur while parsing integers. 31 | #[error("Failed to parse integer: `{0}`")] 32 | IntParseError(#[from] std::num::TryFromIntError), 33 | /// Error that may occur while analyzing library dependencies. 34 | #[error("Dependency analysis error: `{0}`")] 35 | LddError(#[from] lddtree::Error), 36 | } 37 | 38 | /// Type alias for the standard [`Result`] type. 39 | pub type Result = std::result::Result; 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use pretty_assertions::assert_eq; 45 | use std::io::{Error as IoError, ErrorKind}; 46 | 47 | #[test] 48 | fn test_error() { 49 | let message = "your computer is on fire!"; 50 | let error = Error::from(IoError::new(ErrorKind::Other, message)); 51 | assert_eq!(format!("IO error: `{message}`"), error.to_string()); 52 | assert_eq!( 53 | format!("\"IO error: `{message}`\""), 54 | format!("{:?}", error.to_string()) 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use bytesize::ByteSize; 2 | use sysinfo::{Gid, Groups, Uid, Users}; 3 | 4 | use crate::error::Result; 5 | use std::{ 6 | fs::{self, File, OpenOptions}, 7 | path::PathBuf, 8 | time::{Duration, SystemTime, UNIX_EPOCH}, 9 | }; 10 | 11 | #[cfg(not(target_os = "windows"))] 12 | use std::os::unix::fs::{MetadataExt, PermissionsExt}; 13 | 14 | #[cfg(target_os = "windows")] 15 | use std::os::windows::fs::MetadataExt; 16 | 17 | /// General file information. 18 | #[derive(Debug)] 19 | pub struct FileInfo<'a> { 20 | /// Path of the file. 21 | pub path: &'a str, 22 | /// Arguments of the file. 23 | pub arguments: Option>, 24 | /// Bytes of the file. 25 | pub bytes: &'a [u8], 26 | /// Whether if the file is read only. 27 | pub is_read_only: bool, 28 | /// Name of the file. 29 | pub name: String, 30 | /// Size of the file. 31 | pub size: String, 32 | /// Number of blocks allocated for the file. 33 | pub blocks: u64, 34 | /// Block size. 35 | pub block_size: u64, 36 | /// Device ID. 37 | pub device: u64, 38 | /// Inode number. 39 | pub inode: u64, 40 | /// Number of hard links. 41 | pub links: u64, 42 | /// Access information. 43 | pub access: FileAccessInfo, 44 | /// Date information. 45 | pub date: FileDateInfo, 46 | } 47 | 48 | /// Access information. 49 | #[derive(Debug)] 50 | pub struct FileAccessInfo { 51 | /// Access mode. 52 | pub mode: String, 53 | /// Accessed user. 54 | pub uid: String, 55 | /// Accessed group. 56 | pub gid: String, 57 | } 58 | 59 | /// Date information. 60 | #[derive(Debug)] 61 | pub struct FileDateInfo { 62 | /// Access date. 63 | pub access: String, 64 | /// Modify date. 65 | pub modify: String, 66 | /// Change date. 67 | pub change: String, 68 | /// Birth date. 69 | pub birth: String, 70 | } 71 | 72 | impl<'a> FileInfo<'a> { 73 | /// Constructs a new instance. 74 | #[cfg(not(target_os = "windows"))] 75 | pub fn new(path: &'a str, arguments: Option>, bytes: &'a [u8]) -> Result { 76 | let metadata = fs::metadata(path)?; 77 | let mode = metadata.permissions().mode(); 78 | 79 | let users = Users::new_with_refreshed_list(); 80 | let groups = Groups::new_with_refreshed_list(); 81 | Ok(Self { 82 | path, 83 | arguments, 84 | bytes, 85 | is_read_only: false, 86 | name: PathBuf::from(path) 87 | .file_name() 88 | .map(|v| v.to_string_lossy().to_string()) 89 | .unwrap_or_default(), 90 | size: ByteSize(metadata.len()).to_string(), 91 | blocks: metadata.blocks(), 92 | block_size: metadata.blksize(), 93 | device: metadata.dev(), 94 | inode: metadata.ino(), 95 | links: metadata.nlink(), 96 | access: FileAccessInfo { 97 | mode: format!("{:04o}/{}", mode & 0o777, { 98 | let mut s = String::new(); 99 | s.push(if mode & 0o400 != 0 { 'r' } else { '-' }); 100 | s.push(if mode & 0o200 != 0 { 'w' } else { '-' }); 101 | s.push(if mode & 0o100 != 0 { 'x' } else { '-' }); 102 | s.push(if mode & 0o040 != 0 { 'r' } else { '-' }); 103 | s.push(if mode & 0o020 != 0 { 'w' } else { '-' }); 104 | s.push(if mode & 0o010 != 0 { 'x' } else { '-' }); 105 | s.push(if mode & 0o004 != 0 { 'r' } else { '-' }); 106 | s.push(if mode & 0o002 != 0 { 'w' } else { '-' }); 107 | s.push(if mode & 0o001 != 0 { 'x' } else { '-' }); 108 | s 109 | }), 110 | uid: format!( 111 | "{}/{}", 112 | metadata.uid(), 113 | Uid::try_from(metadata.uid() as usize) 114 | .ok() 115 | .and_then(|uid| users.get_user_by_id(&uid)) 116 | .map(|v| v.name()) 117 | .unwrap_or("?") 118 | ), 119 | gid: format!( 120 | "{}/{}", 121 | metadata.gid(), 122 | groups 123 | .list() 124 | .iter() 125 | .find(|g| Gid::try_from(metadata.gid() as usize).as_ref() == Ok(g.id())) 126 | .map(|v| v.name()) 127 | .unwrap_or("?") 128 | ), 129 | }, 130 | date: { 131 | // Helper function to format SystemTime 132 | fn format_system_time(system_time: SystemTime) -> String { 133 | let datetime: chrono::DateTime = system_time.into(); 134 | format!("{}", datetime.format("%Y-%m-%d %H:%M:%S.%f %z")) 135 | } 136 | FileDateInfo { 137 | access: format_system_time(metadata.accessed()?), 138 | modify: format_system_time(metadata.modified()?), 139 | change: format_system_time( 140 | UNIX_EPOCH 141 | + Duration::new( 142 | metadata.ctime().try_into()?, 143 | metadata.ctime_nsec().try_into()?, 144 | ), 145 | ), 146 | birth: metadata 147 | .created() 148 | .map(format_system_time) 149 | .unwrap_or_else(|_| String::from("not supported")), 150 | } 151 | }, 152 | }) 153 | } 154 | 155 | #[cfg(target_os = "windows")] 156 | pub fn new(path: &'a str, arguments: Option>, bytes: &'a [u8]) -> Result { 157 | let metadata = fs::metadata(path)?; 158 | 159 | let users = Users::new_with_refreshed_list(); 160 | let groups = Groups::new_with_refreshed_list(); 161 | Ok(Self { 162 | path, 163 | arguments, 164 | bytes, 165 | is_read_only: false, 166 | name: PathBuf::from(path) 167 | .file_name() 168 | .map(|v| v.to_string_lossy().to_string()) 169 | .unwrap_or_default(), 170 | size: ByteSize(metadata.len()).to_string(), 171 | blocks: 0, // Not applicable for Windows 172 | block_size: 0, // Not applicable for Windows 173 | device: 0, // Not applicable for Windows 174 | inode: 0, // Not applicable for Windows 175 | links: 1, // Windows does not have links like Unix 176 | access: FileAccessInfo { 177 | mode: String::from("unsupported"), 178 | uid: String::from("unsupported"), 179 | gid: String::from("unsupported"), 180 | }, 181 | date: { 182 | // Helper function to format SystemTime 183 | fn format_system_time(system_time: SystemTime) -> String { 184 | let datetime: chrono::DateTime = system_time.into(); 185 | format!("{}", datetime.format("%Y-%m-%d %H:%M:%S.%f %z")) 186 | } 187 | FileDateInfo { 188 | access: format_system_time(metadata.accessed()?), 189 | modify: format_system_time(metadata.modified()?), 190 | change: String::from("unsupported"), 191 | birth: metadata 192 | .created() 193 | .map(format_system_time) 194 | .unwrap_or_else(|_| String::from("not supported")), 195 | } 196 | }, 197 | }) 198 | } 199 | 200 | /// Opens the file (with R/W if possible) and returns it. 201 | pub fn open_file(&mut self) -> Result { 202 | Ok( 203 | match OpenOptions::new().write(true).read(true).open(self.path) { 204 | Ok(v) => v, 205 | Err(_) => { 206 | self.is_read_only = true; 207 | File::open(self.path)? 208 | } 209 | }, 210 | ) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **binsider** - Analyze ELF binaries like a boss 😼🕵️‍♂️ 2 | //! 3 | //! See the [documentation](https://binsider.dev) for more information. 4 | 5 | #![warn(missing_docs, clippy::unwrap_used)] 6 | 7 | /// Main application. 8 | pub mod app; 9 | 10 | /// Terminal user interface. 11 | pub mod tui; 12 | 13 | /// ELF helper. 14 | pub mod elf; 15 | 16 | /// Command-line arguments parser. 17 | pub mod args; 18 | 19 | /// Error handler implementation. 20 | pub mod error; 21 | 22 | /// System call tracer. 23 | #[cfg(feature = "dynamic-analysis")] 24 | pub mod tracer; 25 | 26 | /// File information. 27 | pub mod file; 28 | 29 | /// Common types that can be glob-imported for convenience. 30 | pub mod prelude; 31 | 32 | use args::Args; 33 | use file::FileInfo; 34 | use prelude::*; 35 | use ratatui::backend::CrosstermBackend; 36 | use ratatui::Terminal; 37 | use std::{env, fs, io, path::PathBuf}; 38 | use tui::{state::State, ui::Tab, Tui}; 39 | 40 | /// Runs binsider. 41 | pub fn run(mut args: Args) -> Result<()> { 42 | if args.files.is_empty() { 43 | args.files.push(env::current_exe()?); 44 | } 45 | let mut path = args.files[args.files.len() - 1].clone(); 46 | let mut arguments = None; 47 | let path_str = path.to_string_lossy().to_string(); 48 | let mut parts = path_str.split_whitespace(); 49 | if let Some(bin) = parts.next() { 50 | path = PathBuf::from(bin); 51 | arguments = Some(parts.collect()); 52 | } 53 | if !path.exists() { 54 | let resolved_path = which::which(path.to_string_lossy().to_string())?; 55 | if let Some(file) = args.files.iter_mut().find(|f| **f == path) { 56 | *file = resolved_path.clone(); 57 | } 58 | path = resolved_path; 59 | } 60 | let file_data = fs::read(&path)?; 61 | let bytes = file_data.as_slice(); 62 | let file_info = FileInfo::new(path.to_str().unwrap_or_default(), arguments, bytes)?; 63 | let analyzer = Analyzer::new(file_info, args.min_strings_len, args.files.clone())?; 64 | start_tui(analyzer, args) 65 | } 66 | 67 | /// Starts the terminal user interface. 68 | pub fn start_tui(analyzer: Analyzer, args: Args) -> Result<()> { 69 | // Create an application. 70 | let mut state = State::new(analyzer, args.accent_color)?; 71 | 72 | // Change tab depending on cli arguments. 73 | state.set_tab(args.tab); 74 | 75 | // Initialize the terminal user interface. 76 | let backend = CrosstermBackend::new(io::stdout()); 77 | let terminal = Terminal::new(backend)?; 78 | let events = EventHandler::new(250); 79 | state.analyzer.extract_strings(events.sender.clone()); 80 | let mut tui = Tui::new(terminal, events); 81 | tui.init()?; 82 | 83 | // Start the main loop. 84 | while state.running { 85 | // Render the user interface. 86 | tui.draw(&mut state)?; 87 | // Handle events. 88 | match tui.events.next()? { 89 | Event::Tick => {} 90 | Event::Key(key_event) => { 91 | let command = if state.input_mode { 92 | Command::Input(InputCommand::parse(key_event, &state.input)) 93 | } else if state.show_heh { 94 | Command::Hexdump(HexdumpCommand::parse( 95 | key_event, 96 | state.analyzer.file.is_read_only, 97 | )) 98 | } else { 99 | Command::from(key_event) 100 | }; 101 | state.run_command(command, tui.events.sender.clone())?; 102 | } 103 | Event::Mouse(mouse_event) => { 104 | state.run_command(Command::from(mouse_event), tui.events.sender.clone())?; 105 | } 106 | Event::Resize(_, _) => {} 107 | Event::FileStrings(strings) => { 108 | state.strings_loaded = true; 109 | state.analyzer.strings = Some(strings?.into_iter().map(|(v, l)| (l, v)).collect()); 110 | if state.tab == Tab::Strings { 111 | state.handle_tab()?; 112 | } 113 | } 114 | #[cfg(feature = "dynamic-analysis")] 115 | Event::Trace => { 116 | state.system_calls_loaded = false; 117 | tui.toggle_pause()?; 118 | tracer::trace_syscalls(&state.analyzer.file, tui.events.sender.clone()); 119 | } 120 | #[cfg(feature = "dynamic-analysis")] 121 | Event::TraceResult(syscalls) => { 122 | state.analyzer.tracer = match syscalls { 123 | Ok(v) => v, 124 | Err(e) => TraceData { 125 | syscalls: console::style(e).red().to_string().as_bytes().to_vec(), 126 | ..Default::default() 127 | }, 128 | }; 129 | state.system_calls_loaded = true; 130 | state.dynamic_scroll_index = 0; 131 | tui.toggle_pause()?; 132 | state.handle_tab()?; 133 | } 134 | #[cfg(not(feature = "dynamic-analysis"))] 135 | Event::Trace | Event::TraceResult(_) => {} 136 | Event::Restart(path) => { 137 | let mut args = args.clone(); 138 | match path { 139 | Some(path) => { 140 | args.files.push(path); 141 | } 142 | None => { 143 | args.files.pop(); 144 | } 145 | } 146 | if !args.files.is_empty() { 147 | tui.exit()?; 148 | state.running = false; 149 | run(args)?; 150 | } 151 | } 152 | } 153 | } 154 | 155 | // Exit the user interface. 156 | tui.exit()?; 157 | Ok(()) 158 | } 159 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use binsider::args::Args; 2 | use binsider::error::Result; 3 | use clap::Parser; 4 | use ratatui::style::Color; 5 | use std::process; 6 | use std::time::Duration; 7 | use termbg::Theme; 8 | 9 | fn main() -> Result<()> { 10 | let mut args = Args::parse(); 11 | if args.accent_color.is_none() { 12 | args.accent_color = termbg::theme(Duration::from_millis(10)) 13 | .map(|theme| { 14 | if theme == Theme::Dark { 15 | Color::White 16 | } else { 17 | Color::Black 18 | } 19 | }) 20 | .ok(); 21 | } 22 | match binsider::run(args) { 23 | Ok(_) => process::exit(0), 24 | Err(e) => { 25 | eprintln!("{e}"); 26 | process::exit(1) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use super::app::*; 2 | pub use super::error::*; 3 | pub use super::file::*; 4 | pub use super::tui::command::*; 5 | pub use super::tui::event::*; 6 | pub use super::tui::state::*; 7 | pub use super::tui::ui::*; 8 | -------------------------------------------------------------------------------- /src/tracer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | use std::sync::mpsc; 3 | use std::thread; 4 | 5 | use console::Style; 6 | use lurk_cli::args::Args; 7 | use lurk_cli::style::StyleConfig; 8 | use lurk_cli::Tracer; 9 | use nix::sys::wait::{waitpid, WaitPidFlag}; 10 | 11 | use crate::error::{Error, Result}; 12 | use crate::file::FileInfo; 13 | use crate::tui::event::Event; 14 | use crate::TraceData; 15 | 16 | use nix::unistd::{fork, ForkResult}; 17 | 18 | /// Trace system calls and signals. 19 | pub fn trace_syscalls(file: &FileInfo, event_sender: mpsc::Sender) { 20 | let event_sender = event_sender.clone(); 21 | let mut command = vec![file.path.to_string()]; 22 | if let Some(args) = &file.arguments { 23 | command.extend(args.iter().map(|s| s.to_string())); 24 | } 25 | thread::spawn(move || { 26 | let run_tracer = || -> Result<()> { 27 | let pid = match unsafe { fork() } { 28 | Ok(ForkResult::Child) => { 29 | return lurk_cli::run_tracee(&command, &[], &None) 30 | .map_err(|e| Error::TraceError(e.to_string())) 31 | } 32 | Ok(ForkResult::Parent { child }) => child, 33 | Err(err) => return Err(Error::TraceError(format!("fork() failed: {err}"))), 34 | }; 35 | let mut syscalls = Vec::new(); 36 | let mut tracer = Tracer::new( 37 | pid, 38 | Args::default(), 39 | Box::new(Cursor::new(&mut syscalls)), 40 | StyleConfig { 41 | pid: Style::new().cyan(), 42 | syscall: Style::new().white().bold(), 43 | success: Style::new().green(), 44 | error: Style::new().red(), 45 | result: Style::new().yellow(), 46 | use_colors: true, 47 | }, 48 | ) 49 | .map_err(|e| Error::TraceError(e.to_string()))?; 50 | tracer 51 | .run_tracer() 52 | .map_err(|e| Error::TraceError(e.to_string()))?; 53 | 54 | let mut summary = Vec::new(); 55 | tracer.set_output(Box::new(Cursor::new(&mut summary))); 56 | tracer 57 | .report_summary() 58 | .map_err(|e| Error::TraceError(e.to_string()))?; 59 | 60 | let _ = waitpid(pid, Some(WaitPidFlag::WNOHANG)); 61 | event_sender 62 | .send(Event::TraceResult(Ok(TraceData { syscalls, summary }))) 63 | .map_err(|e| Error::ChannelSendError(e.to_string()))?; 64 | Ok(()) 65 | }; 66 | if let Err(e) = run_tracer() { 67 | event_sender 68 | .send(Event::TraceResult(Err(e))) 69 | .expect("failed to send the trace result") 70 | } 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /src/tui/command.rs: -------------------------------------------------------------------------------- 1 | use ratatui::crossterm::event::{ 2 | Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, 3 | }; 4 | use tui_input::Input; 5 | 6 | /// Possible scroll areas. 7 | #[derive(Debug, PartialEq, Eq)] 8 | pub enum ScrollType { 9 | /// Main application tabs. 10 | Tab, 11 | /// Inner tables. 12 | Table, 13 | /// Main list. 14 | List, 15 | /// Block. 16 | Block, 17 | } 18 | 19 | /// Application command. 20 | #[derive(Debug, PartialEq, Eq)] 21 | pub enum Command { 22 | /// Open repository URL. 23 | OpenRepo, 24 | /// Show details. 25 | ShowDetails, 26 | /// Next. 27 | Next(ScrollType, usize), 28 | /// Previous. 29 | Previous(ScrollType, usize), 30 | /// Go to top. 31 | Top, 32 | /// Go to bottom. 33 | Bottom, 34 | /// Increment value. 35 | Increment, 36 | /// Decrement value. 37 | Decrement, 38 | /// Input command. 39 | Input(InputCommand), 40 | /// Hexdump command. 41 | Hexdump(HexdumpCommand), 42 | /// Trace system calls. 43 | TraceCalls, 44 | /// Exit application. 45 | Exit, 46 | /// Do nothing. 47 | Nothing, 48 | /// Change data to human readable format 49 | HumanReadable, 50 | } 51 | 52 | impl From for Command { 53 | fn from(key_event: KeyEvent) -> Self { 54 | match key_event.code { 55 | KeyCode::Right | KeyCode::Char('l') => Self::Next(ScrollType::Table, 1), 56 | KeyCode::Left | KeyCode::Char('h') => Self::Previous(ScrollType::Table, 1), 57 | KeyCode::Char('n') => Self::Next(ScrollType::Block, 1), 58 | KeyCode::Char('p') => Self::Previous(ScrollType::Block, 1), 59 | KeyCode::Down | KeyCode::Char('j') => Self::Next(ScrollType::List, 1), 60 | KeyCode::Up | KeyCode::Char('k') => Self::Previous(ScrollType::List, 1), 61 | KeyCode::PageDown => Self::Next(ScrollType::List, 5), 62 | KeyCode::PageUp => Self::Previous(ScrollType::List, 5), 63 | KeyCode::Char('d') => { 64 | if key_event.modifiers == KeyModifiers::CONTROL { 65 | Self::Next(ScrollType::List, 5) 66 | } else { 67 | Self::Nothing 68 | } 69 | } 70 | KeyCode::Char('u') => { 71 | if key_event.modifiers == KeyModifiers::CONTROL { 72 | Self::Previous(ScrollType::List, 5) 73 | } else { 74 | Self::Nothing 75 | } 76 | } 77 | KeyCode::Esc | KeyCode::Char('q') => Self::Exit, 78 | KeyCode::Tab => Self::Next(ScrollType::Tab, 1), 79 | KeyCode::BackTab => Self::Previous(ScrollType::Tab, 1), 80 | KeyCode::Char('t') | KeyCode::Home => Self::Top, 81 | KeyCode::Char('b') | KeyCode::End => Self::Bottom, 82 | KeyCode::Char('+') => Self::Increment, 83 | KeyCode::Char('-') => Self::Decrement, 84 | KeyCode::Char('c') | KeyCode::Char('C') => { 85 | if key_event.modifiers == KeyModifiers::CONTROL { 86 | Self::Exit 87 | } else { 88 | Self::Nothing 89 | } 90 | } 91 | KeyCode::Char('/') => Self::Input(InputCommand::Enter), 92 | KeyCode::Char('f') => { 93 | if key_event.modifiers == KeyModifiers::CONTROL { 94 | Self::Input(InputCommand::Enter) 95 | } else { 96 | Self::Nothing 97 | } 98 | } 99 | KeyCode::Backspace => Self::Input(InputCommand::Resume(Event::Key(key_event))), 100 | KeyCode::Enter => Self::ShowDetails, 101 | KeyCode::Char('o') => Self::OpenRepo, 102 | KeyCode::Char('r') => Self::TraceCalls, 103 | KeyCode::Char('s') => Self::HumanReadable, 104 | _ => Self::Nothing, 105 | } 106 | } 107 | } 108 | 109 | impl From for Command { 110 | fn from(mouse_event: MouseEvent) -> Self { 111 | match mouse_event.kind { 112 | MouseEventKind::ScrollDown => Self::Next(ScrollType::List, 1), 113 | MouseEventKind::ScrollUp => Self::Previous(ScrollType::List, 1), 114 | _ => Self::Nothing, 115 | } 116 | } 117 | } 118 | 119 | /// Input mode command. 120 | #[derive(Debug, PartialEq, Eq)] 121 | pub enum InputCommand { 122 | /// Handle input. 123 | Handle(Event), 124 | /// Enter input mode. 125 | Enter, 126 | /// Confirm input. 127 | Confirm, 128 | /// Resume input. 129 | Resume(Event), 130 | /// Exit input mode 131 | Exit, 132 | } 133 | 134 | impl InputCommand { 135 | /// Parses the event. 136 | pub fn parse(key_event: KeyEvent, input: &Input) -> Self { 137 | if key_event.code == KeyCode::Esc 138 | || (key_event.code == KeyCode::Backspace && input.value().is_empty()) 139 | { 140 | Self::Exit 141 | } else if key_event.code == KeyCode::Enter { 142 | Self::Confirm 143 | } else { 144 | Self::Handle(Event::Key(key_event)) 145 | } 146 | } 147 | } 148 | 149 | /// Hexdump command. 150 | #[derive(Debug, PartialEq, Eq)] 151 | pub enum HexdumpCommand { 152 | /// Handle hexdump event. 153 | Handle(Event), 154 | /// Handle hexdump event with a custom key. 155 | HandleCustom(Event, Event), 156 | /// Warn. 157 | Warn(String, Event), 158 | /// Cancel hexdump and move to the next tab. 159 | CancelNext, 160 | /// Cancel hexdump and move to the previous tab. 161 | CancelPrevious, 162 | /// Exit application. 163 | Exit(Event), 164 | } 165 | 166 | impl HexdumpCommand { 167 | /// Parses the event. 168 | pub fn parse(key_event: KeyEvent, is_read_only: bool) -> Self { 169 | match key_event.code { 170 | KeyCode::Char('q') => Self::Exit(Event::Key(key_event)), 171 | KeyCode::Tab => Self::CancelNext, 172 | KeyCode::BackTab => Self::CancelPrevious, 173 | KeyCode::Char('s') => { 174 | if is_read_only { 175 | Self::Warn(String::from("file is read-only"), Event::Key(key_event)) 176 | } else { 177 | Self::HandleCustom( 178 | Event::Key(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL)), 179 | Event::Key(key_event), 180 | ) 181 | } 182 | } 183 | KeyCode::Char('g') => Self::HandleCustom( 184 | Event::Key(KeyEvent::new(KeyCode::Char('j'), KeyModifiers::CONTROL)), 185 | Event::Key(key_event), 186 | ), 187 | KeyCode::Char('n') => Self::HandleCustom( 188 | Event::Key(KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL)), 189 | Event::Key(key_event), 190 | ), 191 | _ => Self::Handle(Event::Key(key_event)), 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/tui/event.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::TraceData; 3 | use ratatui::crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent}; 4 | use std::path::PathBuf; 5 | use std::sync::{ 6 | atomic::{AtomicBool, Ordering}, 7 | mpsc, Arc, 8 | }; 9 | use std::thread; 10 | use std::time::{Duration, Instant}; 11 | 12 | /// Terminal events. 13 | #[derive(Debug)] 14 | pub enum Event { 15 | /// Terminal tick. 16 | Tick, 17 | /// Key press. 18 | Key(KeyEvent), 19 | /// Mouse click/scroll. 20 | Mouse(MouseEvent), 21 | /// Terminal resize. 22 | Resize(u16, u16), 23 | /// Result of `strings` call. 24 | FileStrings(Result>), 25 | /// Trace system calls. 26 | Trace, 27 | /// Results of tracer. 28 | TraceResult(Result), 29 | /// Restart TUI with a new path. 30 | Restart(Option), 31 | } 32 | 33 | /// Terminal event handler. 34 | #[allow(dead_code)] 35 | #[derive(Debug)] 36 | pub struct EventHandler { 37 | /// Event sender channel. 38 | pub sender: mpsc::Sender, 39 | /// Event receiver channel. 40 | receiver: mpsc::Receiver, 41 | /// Event handler thread. 42 | handler: thread::JoinHandle<()>, 43 | /// Is the key input disabled? 44 | pub key_input_disabled: Arc, 45 | /// Is it listening for events? 46 | running: Arc, 47 | } 48 | 49 | impl EventHandler { 50 | /// Constructs a new instance of [`EventHandler`]. 51 | pub fn new(tick_rate: u64) -> Self { 52 | let tick_rate = Duration::from_millis(tick_rate); 53 | let (sender, receiver) = mpsc::channel(); 54 | let key_input_disabled = Arc::new(AtomicBool::new(false)); 55 | let running = Arc::new(AtomicBool::new(true)); 56 | let handler = { 57 | let sender = sender.clone(); 58 | let key_input_disabled = key_input_disabled.clone(); 59 | let running = running.clone(); 60 | thread::spawn(move || { 61 | let mut last_tick = Instant::now(); 62 | while running.load(Ordering::Relaxed) { 63 | let timeout = tick_rate 64 | .checked_sub(last_tick.elapsed()) 65 | .unwrap_or(tick_rate); 66 | if key_input_disabled.load(Ordering::Relaxed) { 67 | thread::sleep(timeout); 68 | continue; 69 | } else if event::poll(timeout).expect("no events available") { 70 | match event::read().expect("unable to read event") { 71 | CrosstermEvent::Key(e) => sender.send(Event::Key(e)), 72 | CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), 73 | CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), 74 | _ => unimplemented!(), 75 | } 76 | .expect("failed to send terminal event") 77 | } 78 | 79 | if last_tick.elapsed() >= tick_rate { 80 | sender.send(Event::Tick).expect("failed to send tick event"); 81 | last_tick = Instant::now(); 82 | } 83 | } 84 | }) 85 | }; 86 | Self { 87 | sender, 88 | receiver, 89 | handler, 90 | key_input_disabled, 91 | running, 92 | } 93 | } 94 | 95 | /// Receive the next event from the handler thread. 96 | /// 97 | /// This function will always block the current thread if 98 | /// there is no data available and it's possible for more data to be sent. 99 | pub fn next(&self) -> Result { 100 | Ok(self.receiver.recv()?) 101 | } 102 | 103 | /// Stops the event listener. 104 | pub fn stop(&self) { 105 | self.running.store(false, Ordering::Relaxed); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/tui/mod.rs: -------------------------------------------------------------------------------- 1 | /// Application state handler. 2 | pub mod state; 3 | 4 | /// Terminal events handler. 5 | pub mod event; 6 | 7 | /// Widget renderer. 8 | pub mod ui; 9 | 10 | /// Custom widgets. 11 | pub mod widgets; 12 | 13 | /// Possible commands. 14 | pub mod command; 15 | 16 | use crate::error::Result; 17 | use event::EventHandler; 18 | use ratatui::backend::{Backend, CrosstermBackend}; 19 | use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture}; 20 | use ratatui::crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; 21 | use ratatui::Terminal; 22 | use state::State; 23 | use std::sync::atomic::Ordering; 24 | use std::{io, panic}; 25 | 26 | /// Representation of a terminal user interface. 27 | /// 28 | /// It is responsible for setting up the terminal, 29 | /// initializing the interface and handling the draw events. 30 | #[derive(Debug)] 31 | pub struct Tui { 32 | /// Interface to the Terminal. 33 | terminal: Terminal, 34 | /// Terminal event handler. 35 | pub events: EventHandler, 36 | /// Is the interface paused? 37 | pub paused: bool, 38 | } 39 | 40 | impl Tui { 41 | /// Constructs a new instance of [`Tui`]. 42 | pub fn new(terminal: Terminal, events: EventHandler) -> Self { 43 | Self { 44 | terminal, 45 | events, 46 | paused: false, 47 | } 48 | } 49 | 50 | /// Initializes the terminal interface. 51 | /// 52 | /// It enables the raw mode and sets terminal properties. 53 | pub fn init(&mut self) -> Result<()> { 54 | terminal::enable_raw_mode()?; 55 | ratatui::crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?; 56 | panic::set_hook(Box::new(move |panic| { 57 | Self::reset().expect("failed to reset the terminal"); 58 | better_panic::Settings::auto() 59 | .most_recent_first(false) 60 | .lineno_suffix(true) 61 | .create_panic_handler()(panic); 62 | std::process::exit(1); 63 | })); 64 | self.terminal.hide_cursor()?; 65 | self.terminal.clear()?; 66 | Ok(()) 67 | } 68 | 69 | /// [`Draw`] the terminal interface by [`rendering`] the widgets. 70 | /// 71 | /// [`Draw`]: tui::Terminal::draw 72 | /// [`rendering`]: crate::ui:render 73 | pub fn draw(&mut self, app: &mut State) -> Result<()> { 74 | self.terminal.draw(|frame| ui::render(app, frame))?; 75 | Ok(()) 76 | } 77 | 78 | /// Toggles the [`paused`] state of interface. 79 | /// 80 | /// It disables the key input and exits the 81 | /// terminal interface on pause (and vice-versa). 82 | /// 83 | /// [`paused`]: Tui::paused 84 | pub fn toggle_pause(&mut self) -> Result<()> { 85 | self.paused = !self.paused; 86 | if self.paused { 87 | Self::reset()?; 88 | } else { 89 | self.init()?; 90 | } 91 | self.events 92 | .key_input_disabled 93 | .store(self.paused, Ordering::Relaxed); 94 | Ok(()) 95 | } 96 | 97 | /// Reset the terminal interface. 98 | /// 99 | /// It disables the raw mode and reverts back the terminal properties. 100 | pub fn reset() -> Result<()> { 101 | terminal::disable_raw_mode()?; 102 | ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; 103 | Terminal::new(CrosstermBackend::new(io::stdout()))?.show_cursor()?; 104 | Ok(()) 105 | } 106 | 107 | /// Exits the terminal interface. 108 | /// 109 | /// It disables the raw mode and reverts back the terminal properties. 110 | pub fn exit(&mut self) -> Result<()> { 111 | terminal::disable_raw_mode()?; 112 | ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; 113 | self.terminal.show_cursor()?; 114 | self.events.stop(); 115 | Ok(()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/tui/widgets/list.rs: -------------------------------------------------------------------------------- 1 | use ratatui::widgets::TableState as State; 2 | 3 | /// List widget with TUI controlled states. 4 | #[derive(Debug)] 5 | pub struct SelectableList { 6 | /// List items. 7 | pub items: Vec, 8 | /// State that can be modified by TUI. 9 | pub state: State, 10 | } 11 | 12 | impl Default for SelectableList { 13 | fn default() -> Self { 14 | Self::with_items(Vec::new()) 15 | } 16 | } 17 | 18 | impl SelectableList { 19 | /// Constructs a new instance of `SelectableList`. 20 | pub fn new(items: Vec, mut state: State) -> SelectableList { 21 | state.select(Some(0)); 22 | Self { items, state } 23 | } 24 | 25 | /// Construct a new `SelectableList` with given items. 26 | pub fn with_items(items: Vec) -> SelectableList { 27 | Self::new(items, State::default()) 28 | } 29 | 30 | /// Returns the selected item. 31 | pub fn selected(&self) -> Option<&T> { 32 | self.items.get(self.state.selected()?) 33 | } 34 | 35 | /// Selects the first item. 36 | pub fn first(&mut self) { 37 | self.state.select(Some(0)); 38 | } 39 | 40 | /// Selects the last item. 41 | pub fn last(&mut self) { 42 | self.state.select(Some(self.items.len().saturating_sub(1))); 43 | } 44 | 45 | /// Selects the next item. 46 | pub fn next(&mut self, amount: usize) { 47 | let i = match self.state.selected() { 48 | Some(i) => { 49 | if i.saturating_add(amount) >= self.items.len() { 50 | 0 51 | } else { 52 | i.saturating_add(amount) 53 | } 54 | } 55 | None => 0, 56 | }; 57 | self.state.select(Some(i)); 58 | } 59 | 60 | /// Selects the previous item. 61 | pub fn previous(&mut self, amount: usize) { 62 | let i = match self.state.selected() { 63 | Some(i) => { 64 | if i == 0 { 65 | self.items.len().saturating_sub(1) 66 | } else { 67 | i.saturating_sub(amount) 68 | } 69 | } 70 | None => 0, 71 | }; 72 | self.state.select(Some(i)); 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | 80 | #[test] 81 | fn test_selectable_list() { 82 | let mut list = SelectableList::with_items(vec!["data1", "data2", "data3"]); 83 | list.state.select(Some(1)); 84 | assert_eq!(Some(&"data2"), list.selected()); 85 | list.next(1); 86 | assert_eq!(Some(2), list.state.selected()); 87 | list.previous(1); 88 | assert_eq!(Some(1), list.state.selected()); 89 | 90 | let mut list = SelectableList::<()>::default(); 91 | list.state.select(None); 92 | list.next(1); 93 | list.state.select(None); 94 | list.previous(1); 95 | assert_eq!(Some(0), list.state.selected()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/tui/widgets/logo.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use ansi_to_tui::IntoText; 4 | use ratatui::{ 5 | buffer::Buffer, 6 | layout::Rect, 7 | style::{Color, Style, Stylize}, 8 | text::Text, 9 | widgets::{Widget, WidgetRef}, 10 | }; 11 | 12 | const LOGO: &str = " 13 | \x1b[49m \x1b[48;2;22;22;22m \x1b[38;2;22;22;22;49m▄\x1b[49m \x1b[38;2;22;22;22;49m▄\x1b[48;2;22;22;22m \x1b[49m \x1b[m 14 | \x1b[49m \x1b[48;2;22;22;22m \x1b[38;2;22;22;22;49m▄▄▄▄▄▄\x1b[48;2;22;22;22m \x1b[49m \x1b[m 15 | \x1b[49m \x1b[48;2;22;22;22m \x1b[38;2;248;190;117;48;2;22;22;22m▄\x1b[48;2;22;22;22m \x1b[38;2;248;190;117;48;2;22;22;22m▄\x1b[48;2;22;22;22m \x1b[49m \x1b[m 16 | \x1b[49m \x1b[48;2;22;22;22m \x1b[48;2;248;190;117m \x1b[48;2;22;22;22m \x1b[48;2;248;190;117m \x1b[48;2;22;22;22m \x1b[48;2;248;190;117m \x1b[48;2;22;22;22m \x1b[48;2;248;190;117m \x1b[48;2;22;22;22m \x1b[49m \x1b[m 17 | \x1b[49;38;2;22;22;22m▀\x1b[48;2;22;22;22m \x1b[38;2;255;255;255;48;2;22;22;22m▄▄▄▄▄▄▄▄▄▄▄▄▄▄\x1b[48;2;22;22;22m \x1b[49;38;2;22;22;22m▀\x1b[m 18 | \x1b[49m \x1b[49;38;2;117;117;117m\x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[38;2;255;255;255;48;2;22;22;22m▄\x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[38;2;255;255;255;48;2;22;22;22m▄\x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[49m \x1b[m 19 | \x1b[49m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[49m \x1b[m 20 | \x1b[49m \x1b[49;38;2;22;22;22m▀\x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[49;38;2;22;22;22m▀\x1b[49m \x1b[m 21 | \x1b[49m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[48;2;255;255;255m \x1b[48;2;22;22;22m \x1b[49m \x1b[m 22 | \x1b[49m \x1b[48;2;22;22;22m \x1b[38;2;22;22;22;48;2;104;104;104m▄\x1b[48;2;255;255;255m \x1b[38;2;22;22;22;48;2;73;73;73m▄\x1b[48;2;22;22;22m \x1b[49m \x1b[m 23 | \x1b[49m \x1b[49;38;2;22;22;22m▀▀▀▀▀▀▀▀▀▀▀▀\x1b[49m \x1b[m 24 | "; 25 | const WIDTH: u16 = 20; 26 | const HEIGHT: u16 = 12; 27 | 28 | #[derive(Debug)] 29 | pub struct Logo { 30 | pub init_time: Instant, 31 | pub is_rendered: bool, 32 | } 33 | 34 | impl Default for Logo { 35 | fn default() -> Self { 36 | Self { 37 | init_time: Instant::now(), 38 | is_rendered: false, 39 | } 40 | } 41 | } 42 | 43 | impl WidgetRef for Logo { 44 | fn render_ref(&self, area: Rect, buf: &mut Buffer) { 45 | let text: Text = LOGO.into_text().expect("failed to parse ANSI"); 46 | text.render(area, buf); 47 | let message = "Loading..."; 48 | buf.set_string( 49 | WIDTH / 2 - message.len() as u16 / 2 + area.x, 50 | HEIGHT + area.y, 51 | message, 52 | Style::default().fg(Color::Rgb(248, 190, 117)).italic(), 53 | ) 54 | } 55 | } 56 | 57 | impl Logo { 58 | /// Returns the size of the logo. 59 | pub fn get_size(&self) -> (u16, u16) { 60 | (WIDTH, HEIGHT) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tui/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | /// ANSI logo. 2 | pub(crate) mod logo; 3 | 4 | /// Stateful list. 5 | pub mod list; 6 | -------------------------------------------------------------------------------- /tests/app.rs: -------------------------------------------------------------------------------- 1 | use binsider::{app::Analyzer, error::Result, file::FileInfo, prelude::Event}; 2 | use std::{fs, path::PathBuf, sync::mpsc}; 3 | 4 | fn get_test_path() -> PathBuf { 5 | PathBuf::from(env!("CARGO_BIN_EXE_binsider")) 6 | } 7 | 8 | fn get_test_bytes() -> Result> { 9 | let debug_binary = get_test_path(); 10 | Ok(fs::read(debug_binary)?) 11 | } 12 | 13 | #[test] 14 | fn test_init() -> Result<()> { 15 | Analyzer::new( 16 | FileInfo::new( 17 | get_test_path().to_str().expect("failed to get test path"), 18 | None, 19 | get_test_bytes()?.as_slice(), 20 | )?, 21 | 4, 22 | vec![], 23 | ) 24 | .map(|_| ()) 25 | } 26 | 27 | #[test] 28 | fn test_extract_strings() -> Result<()> { 29 | let test_bytes = get_test_bytes()?; 30 | let test_path = get_test_path(); 31 | let mut analyzer = Analyzer::new( 32 | FileInfo::new( 33 | test_path.to_str().expect("failed to get test path"), 34 | None, 35 | test_bytes.as_slice(), 36 | )?, 37 | 4, 38 | vec![], 39 | )?; 40 | let (tx, rx) = mpsc::channel(); 41 | analyzer.extract_strings(tx); 42 | if let Event::FileStrings(strings) = rx.recv()? { 43 | assert!(strings?.iter().map(|(s, _)| s).any(|v| v == ".debug_str")); 44 | } else { 45 | panic!("strings did not succeed"); 46 | } 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | # configuration for https://github.com/crate-ci/typos 2 | 3 | [default.extend-words] 4 | ratatui = "ratatui" 5 | rela = "rela" 6 | siz = "siz" 7 | inh = "inh" 8 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Starlight Starter Kit: Basics 2 | 3 | [![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) 4 | 5 | ``` 6 | npm create astro@latest -- --template starlight 7 | ``` 8 | 9 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics) 10 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics) 11 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics) 12 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs) 13 | 14 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 15 | 16 | ## 🚀 Project Structure 17 | 18 | Inside of your Astro + Starlight project, you'll see the following folders and files: 19 | 20 | ``` 21 | . 22 | ├── public/ 23 | ├── src/ 24 | │ ├── assets/ 25 | │ ├── content/ 26 | │ │ ├── docs/ 27 | │ │ └── config.ts 28 | │ └── env.d.ts 29 | ├── astro.config.mjs 30 | ├── package.json 31 | └── tsconfig.json 32 | ``` 33 | 34 | Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name. 35 | 36 | Images can be added to `src/assets/` and embedded in Markdown with a relative link. 37 | 38 | Static assets, like favicons, can be placed in the `public/` directory. 39 | 40 | ## 🧞 Commands 41 | 42 | All commands are run from the root of the project, from a terminal: 43 | 44 | | Command | Action | 45 | | :------------------------ | :----------------------------------------------- | 46 | | `npm install` | Installs dependencies | 47 | | `npm run dev` | Starts local dev server at `localhost:4321` | 48 | | `npm run build` | Build your production site to `./dist/` | 49 | | `npm run preview` | Preview your build locally, before deploying | 50 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 51 | | `npm run astro -- --help` | Get help using the Astro CLI | 52 | 53 | ## 👀 Want to learn more? 54 | 55 | Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). 56 | -------------------------------------------------------------------------------- /website/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import starlight from "@astrojs/starlight"; 3 | import tailwind from "@astrojs/tailwind"; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | site: "https://binsider.dev", 8 | integrations: [ 9 | starlight({ 10 | title: "Binsider", 11 | social: { 12 | github: "https://github.com/orhun/binsider", 13 | mastodon: "https://fosstodon.org/@orhun", 14 | "x.com": "https://twitter.com/orhundev", 15 | linkedin: "https://www.linkedin.com/in/orhunp", 16 | discord: "https://discord.gg/zphNxEcEK7", 17 | }, 18 | logo: { 19 | dark: "./src/assets/binsider-logo-dark.png", 20 | light: "./src/assets/binsider-logo-light.png", 21 | replacesTitle: true, 22 | }, 23 | head: [ 24 | { 25 | tag: 'link', 26 | attrs: { 27 | rel: 'apple-touch-icon', 28 | href: '/apple-touch-icon.png', 29 | }, 30 | }, 31 | ], 32 | components: { 33 | Head: "./src/components/Head.astro", 34 | Header: "./src/components/Header.astro", 35 | Header: "./src/components/Header.astro", 36 | Hero: "./src/components/Hero.astro", 37 | Footer: "./src/components/Footer.astro", 38 | }, 39 | customCss: [ 40 | "@fontsource/dejavu-sans/400.css", 41 | "./src/tailwind.css", 42 | "./src/styles/custom.css", 43 | ], 44 | sidebar: [ 45 | { 46 | label: "Getting Started", 47 | autogenerate: { 48 | directory: "getting-started", 49 | }, 50 | }, 51 | { 52 | label: "Installation", 53 | collapsed: true, 54 | autogenerate: { 55 | directory: "installation", 56 | }, 57 | }, 58 | { 59 | label: "Usage", 60 | autogenerate: { 61 | directory: "usage", 62 | }, 63 | }, 64 | { 65 | label: "Blog", 66 | collapsed: true, 67 | autogenerate: { 68 | directory: "blog", 69 | }, 70 | }, 71 | { 72 | label: "Extras", 73 | collapsed: true, 74 | autogenerate: { 75 | directory: "extras", 76 | }, 77 | }, 78 | "pricing", 79 | ], 80 | editLink: { 81 | baseUrl: "https://github.com/orhun/binsider/edit/main/website", 82 | }, 83 | }), 84 | tailwind({ applyBaseStyles: false }), 85 | ], 86 | }); 87 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.9.2", 14 | "@astrojs/starlight": "^0.25.4", 15 | "@astrojs/starlight-tailwind": "^2.0.3", 16 | "@astrojs/tailwind": "^5.1.0", 17 | "@fontsource/dejavu-sans": "^5.0.11", 18 | "astro": "^4.10.2", 19 | "nanostores": "^0.11.2", 20 | "sharp": "^0.32.5", 21 | "tailwindcss": "^3.4.9", 22 | "typescript": "^5.5.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /website/public/CNAME: -------------------------------------------------------------------------------- 1 | binsider.dev 2 | -------------------------------------------------------------------------------- /website/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/public/apple-touch-icon.png -------------------------------------------------------------------------------- /website/src/assets/binsider-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/binsider-logo-dark.png -------------------------------------------------------------------------------- /website/src/assets/binsider-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/binsider-logo-light.png -------------------------------------------------------------------------------- /website/src/assets/binsider-social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/binsider-social-card.png -------------------------------------------------------------------------------- /website/src/assets/binsider-text-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/binsider-text-dark.png -------------------------------------------------------------------------------- /website/src/assets/binsider-text-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/binsider-text-light.png -------------------------------------------------------------------------------- /website/src/assets/demo/binsider-dynamic-analysis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/demo/binsider-dynamic-analysis.gif -------------------------------------------------------------------------------- /website/src/assets/demo/binsider-general-analysis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/demo/binsider-general-analysis.gif -------------------------------------------------------------------------------- /website/src/assets/demo/binsider-hexdump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/demo/binsider-hexdump.gif -------------------------------------------------------------------------------- /website/src/assets/demo/binsider-static-analysis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/demo/binsider-static-analysis.gif -------------------------------------------------------------------------------- /website/src/assets/demo/binsider-strings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/assets/demo/binsider-strings.gif -------------------------------------------------------------------------------- /website/src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from '@astrojs/starlight/props'; 3 | import Default from '@astrojs/starlight/components/Footer.astro'; 4 | import Stat from "./Footer/Stat.astro"; 5 | 6 | const isHomepage = Astro.props.slug === ''; 7 | --- 8 | 9 | { 10 | isHomepage ? ( 11 |
12 |
13 |
14 |

15 | Built with Rust 16 | & Ratatui 🦀🐁 17 | with 🖤 from @orhun

18 |

19 | 20 | 21 | 22 |
23 |
24 |

© {new Date().getFullYear()} Binsider. All rights reserved.

25 |

26 |

27 |

In memory of Jia Tan 🏴

28 |
29 |
30 | ) : ( 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | 76 | -------------------------------------------------------------------------------- /website/src/components/Footer/Icon.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { name } = Astro.props as { name: keyof typeof paths }; 3 | 4 | const paths = { 5 | github: "M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z", 6 | tag: "M1 7.775V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.752 1.752 0 0 1 1 7.775Zm1.5 0c0 .066.026.13.073.177l6.25 6.25a.25.25 0 0 0 .354 0l5.025-5.025a.25.25 0 0 0 0-.354l-6.25-6.25a.25.25 0 0 0-.177-.073H2.75a.25.25 0 0 0-.25.25ZM6 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z", 7 | star: "M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z" 8 | } 9 | const path = paths[name]; 10 | --- 11 | 12 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /website/src/components/Footer/Stars.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { owner, repo } = Astro.props; 3 | 4 | let starCount = 0; 5 | try { 6 | const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`); 7 | if (response.ok) { 8 | const data = await response.json(); 9 | starCount = data.stargazers_count; 10 | } 11 | } catch (error) { 12 | console.error('Failed to fetch GitHub stars:', error); 13 | } 14 | 15 | const approximate = (count:number) => { 16 | if (count >= 1000) { 17 | const mag = Math.trunc(count / 100) / 10; 18 | return `${mag}k+`; 19 | } else { 20 | return `${count}+`; 21 | } 22 | }; 23 | --- 24 | 25 | 26 |

{approximate(starCount)}

27 |
28 | -------------------------------------------------------------------------------- /website/src/components/Footer/Stat.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Icon from "./Icon.astro"; 3 | import Stars from "./Stars.astro"; 4 | import Version from "./Version.astro"; 5 | --- 6 | 7 |
8 |
orhun/binsider
9 | 10 | 11 |
12 | 13 | 28 | -------------------------------------------------------------------------------- /website/src/components/Footer/Version.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { owner, repo } = Astro.props; 3 | 4 | let latestVersion = "0.0.0"; 5 | try { 6 | const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`); 7 | if (response.ok) { 8 | const data = await response.json(); 9 | if (data.tag_name) { 10 | latestVersion = data.tag_name; 11 | } 12 | } 13 | } catch (error) { 14 | console.error('Failed to fetch the latest version:', error); 15 | } 16 | --- 17 | 18 | 19 | {latestVersion} 20 | 21 | -------------------------------------------------------------------------------- /website/src/components/Head.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from '@astrojs/starlight/props'; 3 | import Default from '@astrojs/starlight/components/Head.astro'; 4 | import socialCard from "../assets/binsider-social-card.png"; 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /website/src/components/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from "@astrojs/starlight/props"; 3 | 4 | import Search from "@astrojs/starlight/components/Search.astro"; 5 | import ThemeSelect from "@astrojs/starlight/components/ThemeSelect.astro"; 6 | import SocialIcons from "@astrojs/starlight/components/SocialIcons.astro"; 7 | import LanguageSelect from "@astrojs/starlight/components/LanguageSelect.astro"; 8 | import Logo from '../components/Logo.astro'; 9 | 10 | --- 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 | 28 |
29 | 30 | -------------------------------------------------------------------------------- /website/src/components/Hero.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from '@astrojs/starlight/props'; 3 | import Default from '@astrojs/starlight/components/Hero.astro'; 4 | 5 | const isHomepage = Astro.props.slug === ''; 6 | 7 | import VideoEmbed from './Video.astro'; 8 | --- 9 | 10 | 11 | { 12 | isHomepage ? ( 13 |
14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |

23 | binsider gets inside of the ELF binaries. 24 |

25 |

26 | It provides powerful tools for both static and dynamic analysis, offering features similar to readelf and strace. 27 | It allows you to easily inspect strings, examine linked libraries, and perform a hexdump, all within a user-friendly terminal user interface. 28 |

29 |
30 | 31 | 32 | 33 | 34 | 35 | ) : ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | 43 | 95 | 96 | 169 | -------------------------------------------------------------------------------- /website/src/components/Logo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from "@astrojs/starlight/props"; 3 | import SiteTitle from "@astrojs/starlight/components/SiteTitle.astro"; 4 | import textLight from '../../src/assets/binsider-text-light.png'; 5 | import textDark from '../../src/assets/binsider-text-dark.png'; 6 | --- 7 | 8 |
9 |
10 | 11 |
12 | 13 |
14 | Light text 19 | Dark text 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /website/src/components/Video.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const videoId = "InhVCQoc5ZE"; 3 | --- 4 | 5 |
6 | 15 |
16 | 17 | 30 | -------------------------------------------------------------------------------- /website/src/content/assets/analyze-library.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/analyze-library.gif -------------------------------------------------------------------------------- /website/src/content/assets/blog/0.2.0-libs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/blog/0.2.0-libs.png -------------------------------------------------------------------------------- /website/src/content/assets/blog/0.2.0-static.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/blog/0.2.0-static.gif -------------------------------------------------------------------------------- /website/src/content/assets/blog/20240917_122011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/blog/20240917_122011.jpg -------------------------------------------------------------------------------- /website/src/content/assets/blog/umami-2024927.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/blog/umami-2024927.png -------------------------------------------------------------------------------- /website/src/content/assets/dynamic-analysis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/dynamic-analysis.gif -------------------------------------------------------------------------------- /website/src/content/assets/dynamic-summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/dynamic-summary.jpg -------------------------------------------------------------------------------- /website/src/content/assets/dynamic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/dynamic.jpg -------------------------------------------------------------------------------- /website/src/content/assets/file-headers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/file-headers.jpg -------------------------------------------------------------------------------- /website/src/content/assets/file-information.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/file-information.jpg -------------------------------------------------------------------------------- /website/src/content/assets/general.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/general.jpg -------------------------------------------------------------------------------- /website/src/content/assets/hexdump-modify.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/hexdump-modify.gif -------------------------------------------------------------------------------- /website/src/content/assets/hexdump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/hexdump.gif -------------------------------------------------------------------------------- /website/src/content/assets/hexdump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/hexdump.jpg -------------------------------------------------------------------------------- /website/src/content/assets/library-path.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/library-path.gif -------------------------------------------------------------------------------- /website/src/content/assets/linked-libraries.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/linked-libraries.jpg -------------------------------------------------------------------------------- /website/src/content/assets/notes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/notes.jpg -------------------------------------------------------------------------------- /website/src/content/assets/quickstart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/quickstart.gif -------------------------------------------------------------------------------- /website/src/content/assets/static-table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/static-table.gif -------------------------------------------------------------------------------- /website/src/content/assets/static.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/static.jpg -------------------------------------------------------------------------------- /website/src/content/assets/strings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/strings.gif -------------------------------------------------------------------------------- /website/src/content/assets/strings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhun/binsider/338d919a4992aaf267fcc87c559198532c119d7f/website/src/content/assets/strings.jpg -------------------------------------------------------------------------------- /website/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from 'astro:content'; 2 | import { docsSchema } from '@astrojs/starlight/schema'; 3 | 4 | export const collections = { 5 | docs: defineCollection({ schema: docsSchema() }), 6 | }; 7 | -------------------------------------------------------------------------------- /website/src/content/docs/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "404" 3 | template: splash 4 | editUrl: false 5 | --- 6 | 7 | The cat is not found in the bin. 🗑️ 8 | 9 | Check the URL or try using the search bar. 10 | -------------------------------------------------------------------------------- /website/src/content/docs/blog/v0.2.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Two weeks of binsider (0.2.0)" 3 | --- 4 | 5 | :::tip[😼] 6 | 7 | 8 | 9 | 10 | `binsider` is a terminal UI tool for analyzing the binary files. 11 | 12 | Take a look at the [documentation](https://binsider.dev/) if you are new around here 😎 13 | 14 | ::: 15 | 16 | ### Stats 17 | 18 | It's been only two weeks and `binsider` already reached **1.7k** stars on [GitHub](https://github.com/orhun/binsider): 19 | 20 | 21 | 22 | Star History Chart 23 | Star History Chart 24 | 25 | 26 | 27 |
28 | 29 | Analytics from [Umami](https://umami.orhun.dev/share/Zbh61KU5IaDT4DiZ/binsider.dev): 30 | 31 | ![dynamic analysis](../../assets/blog/umami-2024927.png) 32 | 33 | I would like to thank everyone who supported the project by starring, sharing, and contributing! 🖤 34 | 35 | ### Discord 36 | 37 | I created a Discord server for discussing the development, features, and issues: 38 | 39 | 40 | 41 | 42 | If you are a reverse engineering enthusiast, Rust developer, or just curious about the project, feel free to join the server by clicking the link above! 43 | 44 | ## What's new? 45 | 46 | Click [here](https://github.com/orhun/binsider/blob/main/CHANGELOG.md) for the full changelog. 47 | 48 | ### Better platform support 49 | 50 | The [dynamic analysis feature](/usage/dynamic-analysis) is now optional and gated behind the `dynamic-analysis` feature flag. 51 | 52 | This change allows `binsider` to run on platforms where the dynamic analysis is not supported, e.g., macOS and Windows. 53 | 54 | To build, run the following command: 55 | 56 | ```bash 57 | cargo build --no-default-features 58 | ``` 59 | 60 | ### Run with args 61 | 62 | Now it is possible to [dynamically analyze](/usage/dynamic-analysis) binaries with their CLI arguments. This feature is useful for analyzing binaries that require specific arguments to run correctly, e.g: 63 | 64 | ```sh 65 | binsider "python test.py" 66 | ``` 67 | 68 | ### Nix flake 69 | 70 | I know y'all like Nix, so we have a Nix flake for `binsider` now! ❄️ 71 | 72 | ```bash 73 | nix run "github:orhun/binsider" 74 | ``` 75 | 76 | ### `binsider` as a library 77 | 78 | `binsider` is now available as a Rust library if you want to integrate it into your TUI/Ratatui project! 79 | 80 | ```toml 81 | [dependencies] 82 | binsider = "0.1" 83 | ratatui = "0.28" 84 | ``` 85 | 86 | See the [documentation](/extras/library/) for more information. 87 | 88 | ### Improved white theme support 89 | 90 | Moths and other creatures that prefer light themes can now enjoy `binsider` too! 🦋 91 | 92 | We now check the background color of the terminal and use appropriate accent colors. (e.g. white text on a black background) 93 | 94 | ### Reorder symbol table 95 | 96 | The symbols and dynamic symbols table in [static analysis](/usage/static-analysis) are now reordered for better readability: 97 | 98 | - Name, Type, Value, Size, Bind, Visibility, Ndx 99 | - Name, Requirements, Type, Value, Size, Bind, Visibility, Ndx 100 | 101 | ![static analysis](../../assets/blog/0.2.0-static.gif) 102 | 103 | ### Sorted shared libraries 104 | 105 | The shared libraries in the [general analysis](/usage/general-analysis) are now sorted alphabetically for better readability. 106 | 107 | ![general analysis](../../assets/blog/0.2.0-libs.png) 108 | 109 | ### Squashed bugs 110 | 111 | - _(dynamic)_ Fix locating the binary ([#48](https://github.com/orhun/binsider/pull/48)) 112 | - _(strings)_ Replace unicode whitespace for correct rendering ([#28](https://github.com/orhun/binsider/pull/28)) 113 | - _(file)_ Do not panic if creation time is not supported ([#25](https://github.com/orhun/binsider/pull/25)) 114 | - _(tui)_ Stop the event handler on quit ([#24](https://github.com/orhun/binsider/pull/24)) 115 | - _(flake)_ Fix test failure on Nix ([#30](https://github.com/orhun/binsider/pull/30)) 116 | - _(test)_ Ensure that binary is built before the test runs ([#11](https://github.com/orhun/binsider/pull/11)) 117 | 118 | ## Seeking contributors 119 | 120 | I'm looking for new contributors to help with the development and fixing bugs! 121 | 122 | Here is a list of issues that need attention: 123 | 124 | | Issue | Title | Importance | Type | 125 | | -------------------------------------------------- | -------------------------------------------------------------------------------- | ----------- | ------- | 126 | | [#43](https://github.com/orhun/binsider/issues/43) | Tracing system call error: ESRCH: No such process | Help Needed | Bug | 127 | | [#35](https://github.com/orhun/binsider/issues/35) | Support displaying general file information on Windows | Help Needed | Feature | 128 | | [#17](https://github.com/orhun/binsider/issues/17) | Print the list of linked libraries as a tree and indicate how they were resolved | Medium | Feature | 129 | | [#45](https://github.com/orhun/binsider/issues/45) | Support searching for shared libraries | Medium | Feature | 130 | | [#22](https://github.com/orhun/binsider/issues/22) | Sort Symbols by Name or Address | Medium | Feature | 131 | | [#7](https://github.com/orhun/binsider/issues/7) | Support tweaking dynamic analysis options | Medium | Feature | 132 | | [#5](https://github.com/orhun/binsider/issues/5) | Improve the test suite | Medium | Feature | 133 | | [#47](https://github.com/orhun/binsider/issues/47) | Human readable output for static analysis | Easy | Feature | 134 | | [#6](https://github.com/orhun/binsider/issues/6) | Launch TUI from selected tab | Easy | Feature | 135 | | [54](https://github.com/orhun/binsider/issues/54) | Show the number of shared libraries | Easy | Feature | 136 | 137 | Also, if you are feeling like researching/brainstorming, here are some issues that needs some thought: 138 | 139 | | Issue | Title | 140 | | -------------------------------------------------- | ------------------------------- | 141 | | [#46](https://github.com/orhun/binsider/issues/46) | Support diffing binaries | 142 | | [#26](https://github.com/orhun/binsider/issues/26) | Support more platforms via LIEF | 143 | | [#16](https://github.com/orhun/binsider/issues/16) | Binwalk/unblob | 144 | 145 | Feel free to ask questions in issues or [the Discord server](#discord) - always happy to guide you through the code! 146 | 147 | ## Reproducible builds 148 | 149 | Last week I had the chance to attend the [Reproducible Builds Summit](https://reproducible-builds.org/events/hamburg2024/) in Hamburg, Germany. A lot of topics has been discussed - `binsider` was one of them and I got some feedback/thoughts from people with different backgrounds. 150 | 151 | ![reprobuilds](../../assets/blog/20240917_122011.jpg) 152 | 153 | One of the reoccurring ideas was to add [diffoscope](https://diffoscope.org/) support to `binsider` for comparing binaries. Leave a comment on [#46](https://github.com/orhun/binsider/issues/46) if you think that would be useful - I might start working on it in the upcoming weeks. 154 | 155 | ## Support the development 156 | 157 | [Become a sponsor](https://github.com/sponsors/orhun) of my work 🖤 or simply [buy me a coffee](https://buymeacoffee.com/orhun) ☕ 158 | -------------------------------------------------------------------------------- /website/src/content/docs/extras/library.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using as a Rust Library 3 | --- 4 | 5 | It is possible to use `binsider` in your Rust/TUI application if you are already using [Ratatui](https://ratatui.rs). 6 | 7 | See the [API documentation](https://docs.rs/binsider) for getting information about the available functions and structures. 8 | 9 | To integrate it with your TUI application, add the following to your `Cargo.toml`: 10 | 11 | ```toml 12 | [dependencies] 13 | binsider = "0.1" 14 | ratatui = "0.28" 15 | # use the latest versions above 16 | ``` 17 | 18 | Then you can create a `Analyzer` struct and `State` for managing the TUI state: 19 | 20 | ```rust 21 | use binsider::prelude::*; 22 | 23 | let analyzer = Analyzer::new(file_info, 15, vec![])?; 24 | let mut state = State::new(analyzer, None)?; 25 | ``` 26 | 27 | To render a frame: 28 | 29 | ```rust 30 | terminal.draw(|frame: &mut Frame| { 31 | binsider::tui::ui::render(&mut state, frame); 32 | })?; 33 | ``` 34 | 35 | To handle key events: 36 | 37 | ```rust 38 | let (sender, receiver) = sync::mpsc::channel(); 39 | let command = Command::from(&ratatui::crossterm::event::Event::Key(/* */)); 40 | state.run_command(command, sender.clone())?; 41 | ``` 42 | 43 | See the [demo example](https://github.com/orhun/binsider/blob/main/examples/demo.rs) for the full code. You can run it by `cargo run --example demo`. 44 | -------------------------------------------------------------------------------- /website/src/content/docs/getting-started/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quickstart 3 | --- 4 | 5 | import VideoEmbed from '../../../components/Video.astro'; 6 | 7 | 8 | 9 | Start by [installing](/installation/crates-io) `binsider`: 10 | 11 | ```bash 12 | cargo install binsider 13 | ``` 14 | 15 | Also, check the other installation methods on the left sidebar. 16 | 17 | Now you are ready to dive into the binaries! ✨ Simply run: 18 | 19 | ```bash 20 | binsider 21 | ``` 22 | 23 | You can also run `binsider` without any arguments to analyze the `binsider` binary itself! 24 | 25 | ![demo](../../assets/quickstart.gif) 26 | 27 |
28 | 29 | `binsider` helps you get inside of the ELF binaries. 30 | 31 |
32 | 33 | :::note[What the heck is ELF?] 34 | 35 | [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) (Executable and Linkable Format) is the standard executable format for Linux and other Unix-like operating systems. They are used for executables, shared libraries, and core dumps. 36 | 37 | ::: 38 | 39 | It provides a Terminal User Interface ([TUI](https://en.wikipedia.org/wiki/Text-based_user_interface)) which is designed to perform: 40 | 41 | - [General analysis](/usage/general-analysis) about a file including listing the shared libraries. 42 | - [Static analysis](/usage/static-analysis) for exploring the ELF layout. 43 | - [Dynamic analysis](/usage/dynamic-analysis) for debugging and tracing. 44 | - [String extraction](/usage/strings) for finding interesting strings. 45 | - [Hexdump](/usage/hexdump) for viewing the binary content. 46 | 47 | For detailed documentation on these features, please refer to the links provided above. 48 | 49 | 50 | Happy hacking! 💣 51 | -------------------------------------------------------------------------------- /website/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Binsider 3 | head: 4 | - tag: title 5 | content: Binsider - Analyze ELF binaries like a boss 6 | description: Analyze ELF binaries like a boss. 7 | template: splash 8 | editUrl: true 9 | hero: 10 | tagline: Analyze ELF binaries like a boss. 11 | actions: 12 | - text: Start hacking 13 | link: /getting-started/ 14 | icon: right-arrow 15 | variant: primary 16 | - text: Learn more 17 | link: /usage/general-analysis/ 18 | icon: open-book 19 | - text: Get source 20 | link: "https://github.com/orhun/binsider" 21 | icon: github 22 | --- 23 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/alpine-linux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Alpine Linux 3 | sidebar: 4 | order: 3 5 | --- 6 | 7 | `binsider` is available for [Alpine Edge](https://pkgs.alpinelinux.org/packages?name=binsider&branch=edge) and it can be installed via [apk](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) (Alpine Package Keeper) after enabling the [testing repository](https://wiki.alpinelinux.org/wiki/Repositories): 8 | 9 | ```bash 10 | apk add binsider 11 | ``` 12 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/arch-linux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Arch Linux 3 | sidebar: 4 | order: 2 5 | --- 6 | 7 | `binsider` can be installed from the [official repositories](https://archlinux.org/packages/extra/x86_64/binsider/) using [`pacman`](https://wiki.archlinux.org/title/pacman) on Arch Linux: 8 | 9 | ```bash 10 | pacman -S binsider 11 | ``` 12 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/binary-releases.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Binary releases 3 | sidebar: 4 | order: 4 5 | --- 6 | 7 | See the available binaries for different operating systems/architectures from the [releases page](https://github.com/orhun/binsider/releases). 8 | 9 | :::note 10 | 11 | - Release tarballs for Linux/macOS are signed with the following PGP key: [0C9E792408F77819866E47FA85EF5848473D7F88](https://keyserver.ubuntu.com/pks/lookup?search=0x85EF5848473D7F88&op=vindex) 12 | - If you are using Windows, you can simply download the zip file from the [releases page](https://github.com/orhun/binsider/releases). 13 | 14 | ::: 15 | 16 | 1. Download the binary from [releases](https://github.com/orhun/binsider/releases): 17 | 18 | ```bash 19 | VERSION="0.1.0" 20 | TARGET="x86_64-unknown-linux-gnu.tar.gz" 21 | wget "https://github.com/orhun/binsider/releases/download/v${VERSION}/binsider-${VERSION}-${TARGET}.tar.gz" 22 | ``` 23 | 24 | 2. Extract the files: 25 | 26 | ```bash 27 | tar -xvzf binsider-*.tar.gz 28 | ``` 29 | 30 | 3. Enter the folder and run the binary: 31 | 32 | ```bash 33 | cd "binsider-${version}" 34 | ./binsider 35 | ``` 36 | 37 | 4. Move binary to `/usr/local/bin/` (optional). 38 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/build-from-source.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Build from source 3 | sidebar: 4 | order: 5 5 | --- 6 | 7 | ## Prerequisites 8 | 9 | - [Cargo](https://www.rust-lang.org/) 10 | - The minimum supported Rust version (MSRV) is `1.74.1`. 11 | 12 | ## Instructions 13 | 14 | 1. Clone the repository. 15 | 16 | ```bash 17 | git clone https://github.com/orhun/binsider 18 | cd binsider/ 19 | ``` 20 | 21 | 2. Build. 22 | 23 | ```bash 24 | CARGO_TARGET_DIR=target cargo build --release 25 | ``` 26 | 27 | Binary will be located at `target/release/binsider`. 28 | 29 | :::tip 30 | 31 | If the build fails you can try disabling some of the [feature flags](/installation/crates-io/#features), for example: 32 | 33 | ```bash 34 | CARGO_TARGET_DIR=target cargo build --release --no-default-features 35 | ``` 36 | 37 | ::: 38 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/crates-io.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cargo 3 | sidebar: 4 | order: 1 5 | --- 6 | 7 | `binsider` can be installed from [crates.io](https://crates.io/crates/binsider) if you have [Rust](https://www.rust-lang.org/) installed: 8 | 9 | ```bash 10 | cargo install binsider 11 | ``` 12 | 13 | If you want to install the latest git version: 14 | 15 | ```bash 16 | cargo install --git https://github.com/orhun/binsider 17 | ``` 18 | 19 | :::note 20 | 21 | The minimum supported Rust version (MSRV) is `1.74.1`. 22 | 23 | ::: 24 | 25 | ## Features 26 | 27 | `binsider` supports the following feature flags which can be enabled or disabled during installation: 28 | 29 | - `dynamic-analysis`: Enables the [dynamic analysis](/usage/dynamic-analysis) feature. (default: enabled) 30 | 31 | e.g. To install `binsider` with the `dynamic-analysis` feature disabled: 32 | 33 | ```bash 34 | cargo install binsider --no-default-features 35 | ``` 36 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docker images 3 | sidebar: 4 | order: 6 5 | --- 6 | 7 | Docker images are available at: 8 | 9 | - [Docker Hub](https://hub.docker.com/r/orhunp/binsider) 10 | - [GitHub Container Registry](https://github.com/orhun/binsider/pkgs/container/binsider) 11 | 12 | You can use the following command to run the latest version of `binsider` in a container: 13 | 14 | ```bash 15 | docker run --rm -it "orhunp/binsider:${TAG:-latest}" 16 | ``` 17 | 18 | To analyze a custom binary via mounting a volume: 19 | 20 | ```bash 21 | docker run --rm -it -v "custom:/app/custom:rw" "orhunp/binsider:${TAG:-latest}" custom 22 | ``` 23 | -------------------------------------------------------------------------------- /website/src/content/docs/installation/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Other methods 3 | sidebar: 4 | order: 7 5 | --- 6 | 7 | [![Packaging status](https://repology.org/badge/vertical-allrepos/binsider.svg)](https://repology.org/project/binsider/versions) 8 | 9 | Feel free to package `binsider` for your favorite distribution! ✨ 10 | 11 | :::tip 12 | 13 | If `binsider` is not yet packaged for your platform, you can build it from source. See the [build from source](/installation/build-from-source) guide for instructions. 14 | 15 | ::: 16 | -------------------------------------------------------------------------------- /website/src/content/docs/pricing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pricing 3 | --- 4 | 5 | `binsider` is free and open-source software. 🏴 6 | 7 | It means that you can use it for $0, **forever**. 8 | 9 | However, maintaining and developing software takes time and effort. If you liked what you've seen so far, please: 10 | 11 | - Consider contributing to `binsider`. 12 | 13 | - Check out my other projects: 14 | 15 | - [Ratatui](https://ratatui.rs), which powers the TUI of `binsider`. 16 | - [Git-cliff](https://git-cliff.org/), a highly customizable Changelog Generator. 17 | - Many other tools on my [GitHub profile](https://github.com/orhun). 18 | 19 | [Become a sponsor](https://github.com/sponsors/orhun) of my work 🖤 or simply [buy me a coffee](https://buymeacoffee.com/orhun) ☕ 20 | -------------------------------------------------------------------------------- /website/src/content/docs/usage/dynamic-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dynamic Analysis 3 | sidebar: 4 | order: 3 5 | --- 6 | 7 | ![layout](../../assets/dynamic.jpg) 8 | 9 | Dynamic analysis is the process of examining the binary file **while** it is running. This is useful for debugging and tracing the execution flow. 10 | 11 | For this, `binsider` executes the binary and traces the system calls made by it, similar to the [`strace(1)`](https://man7.org/linux/man-pages/man1/strace.1.html) command. 12 | 13 | Simply press enter to start the dynamic analysis. 14 | 15 | ![dynamic analysis](../../assets/dynamic-analysis.gif) 16 | 17 | :::note[System Calls] 18 | 19 | Each line represents a system call with the following format: 20 | 21 | ```plaintext 22 | [277208] ioctl(1, 21505, 0x7FFFFFFFB900) = -25 23 | ├────────┼─────────────┼─────────────────────┤ 24 | │ │ │ │ 25 | │ │ │ │ 26 | │ │ │ └─ Return value of the system call 27 | │ │ │ 28 | │ │ └─ Arguments 29 | │ │ 30 | │ └─ System call name 31 | │ 32 | └─ Process ID 33 | ``` 34 | 35 | ::: 36 | 37 | You can use the following keys to interact with the dynamic analysis: 38 | 39 | | **Key** | **Action** | 40 | | --------------------------- | -------------------- | 41 | | j / k | Scroll up/down | 42 | | t / b | Go to the top/bottom | 43 | | / | Search for value | 44 | | r | Re-run executable | 45 | | enter | Show details | 46 | 47 | ### Details 48 | 49 | When you press enter, you will get a summary output of the execution: 50 | 51 | ![dynamic summary](../../assets/dynamic-summary.jpg) 52 | 53 | This summary includes the percentage of time spent, time in microseconds, time per call, number of calls, errors, and the corresponding system calls which is useful for understanding the binary's behavior. 54 | 55 | --- 56 | 57 | :::tip[Lurking in the shadows] 58 | 59 | Check out the [`lurk`](https://github.com/JakWai01/lurk) project which powers the dynamic analysis feature of `binsider`. 60 | 61 | ::: 62 | -------------------------------------------------------------------------------- /website/src/content/docs/usage/general-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: General Analysis 3 | sidebar: 4 | order: 1 5 | --- 6 | 7 | ![layout](../../assets/general.jpg) 8 | 9 | On the startup, you can get a general information about the binary file. This information includes: 10 | 11 | - the file size, owner, group, permissions and date information (similar to [`stat(1)`](https://www.man7.org/linux/man-pages/man1/stat.1.html)) 12 | - the shared libraries that are linked (similar to [`ldd(1)`](https://www.man7.org/linux/man-pages/man1/ldd.1.html)) 13 | 14 | --- 15 | 16 | ### File Information 17 | 18 | Here is an example information shown in this tab: 19 | 20 | ![file information](../../assets/file-information.jpg) 21 | 22 | You can press h and l to scroll vertically. 23 | 24 | --- 25 | 26 | ### Linked Shared Libraries 27 | 28 | ![linked libraries](../../assets/linked-libraries.jpg) 29 | 30 | You can press enter to re-run `binsider` with the selected shared library and backspace to go back. This means you can not only analyze a single binary but also the linked shared libraries as well. 31 | 32 | Here is an example of cycling through the shared libraries of `xz` binary: 33 | 34 | ![analyze library](../../assets/analyze-library.gif) 35 | 36 | You can see the analysis path on the top right corner of the screen: 37 | 38 |
39 | 40 | ![library path](../../assets/library-path.gif) 41 | 42 |
43 | -------------------------------------------------------------------------------- /website/src/content/docs/usage/hexdump.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hexdump 3 | sidebar: 4 | order: 5 5 | --- 6 | 7 | ![layout](../../assets/hexdump.jpg) 8 | 9 | Hexdumping is the process of converting binary data into hexadecimal format. This is useful for understanding the binary's structure and finding patterns in the data. 10 | 11 | `binsider` provides a rich dashboard along with a hexdump view to analyze the binary file. 12 | 13 | Here is an example of swimming through the hexdump (h/j/k/l), jumping to a specific byte (g) and searching for a string (/): 14 | 15 | ![hexdump](../../assets/hexdump.gif) 16 | 17 | --- 18 | 19 | ### Modifying Data 20 | 21 | You can also modify the hex data and enter new values, very much like an editor. 22 | 23 | Simply start typing the new hex values and press s to save the changes to the binary. 24 | 25 | ![hexdump](../../assets/hexdump-modify.gif) 26 | 27 | :::danger 28 | 29 | Make sure to back up the binary file before making any changes. 30 | 31 | ::: 32 | 33 | :::caution 34 | 35 | The binary should be opened in read-write mode for this feature to work. 36 | 37 | ::: 38 | 39 | --- 40 | 41 | :::tip[heh!] 42 | 43 | Check out the [`heh`](https://github.com/ndd7xv/heh) project which powers the hexdump feature of `binsider`. 44 | 45 | ::: 46 | -------------------------------------------------------------------------------- /website/src/content/docs/usage/static-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Static Analysis 3 | sidebar: 4 | order: 2 5 | --- 6 | 7 | ![layout](../../assets/static.jpg) 8 | 9 | :::tip 10 | 11 | To select different blocks in this layout and scroll them vertically, you can use the n/p (next/previous) and h/j/k/l keys. Also, press enter to view the details of the selected item. 12 | 13 | ::: 14 | 15 | --- 16 | 17 | ELF files consist of different sections, segments, and headers, each containing information about the binary file. Static analysis is the process of examining these parts to better understand the file's structure **without** executing it. 18 | 19 | :::note[ELF Overview] 20 | 21 | Here is a diagram from [this blog post](https://scratchpad.avikdas.com/elf-explanation/elf-explanation.html) which shows the structure of an ELF file: 22 | 23 | ![ELF overview](https://scratchpad.avikdas.com/elf-explanation/file-overview.svg) 24 | 25 | ::: 26 | 27 | --- 28 | 29 | ### File Headers 30 | 31 | This portion of the layout shows the ELF file headers similar to the output of `readelf -h ` command. It includes the following information: 32 | 33 | ![file headers](../../assets/file-headers.jpg) 34 | 35 | This might come in handy when you want to understand the binary's architecture and the entry point. 36 | 37 | --- 38 | 39 | ### Notes 40 | 41 | Here you can see the notes found in the binary file, similar to the output of `readelf -n ` command. 42 | 43 | ![notes](../../assets/notes.jpg) 44 | 45 | This information is useful for understanding the binary's build environment and the compiler version used. 46 | 47 | --- 48 | 49 | ### Common Sections Table 50 | 51 | This table shows the common sections found in the binary file, similar to the output of `readelf -S ` command. It includes the following information: 52 | 53 | | **Section** | **Description** | 54 | | --------------- | -------------------------------------------------------- | 55 | | Program headers | Segments loaded into memory when the binary is executed. | 56 | | Section headers | Sections storing the binary's data. | 57 | | Symbols | Contains symbols used in the binary. | 58 | | Dynamic symbols | Contains dynamic symbols used in the binary. | 59 | | Dynamic section | Contains dynamic linking information. | 60 | | Relocations | Contains relocations used in the binary. | 61 | 62 | You can press h and l to scroll horizontally and / to search for a specific value. 63 | 64 | ![static table](../../assets/static-table.gif) 65 | -------------------------------------------------------------------------------- /website/src/content/docs/usage/strings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Strings 3 | sidebar: 4 | order: 4 5 | --- 6 | 7 | ![layout](../../assets/strings.jpg) 8 | 9 | Similar to the [`strings(1)`](https://linux.die.net/man/1/strings) command, `binsider` is able to extract strings from the binary file. This is useful for finding interesting strings such as URLs, passwords, and other sensitive information. 10 | 11 | :::tip 12 | 13 | You can adjust the length of the strings to be extracted by using the +/- keys or running `binsider` with the `-n` argument. 14 | 15 | ::: 16 | 17 | ![strings](../../assets/strings.gif) 18 | 19 |
20 | 21 | Press / to search and enter to view the details. 22 | 23 |
24 | -------------------------------------------------------------------------------- /website/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /website/src/slider.js: -------------------------------------------------------------------------------- 1 | import { atom } from "nanostores"; 2 | 3 | export const index = atom(0); 4 | 5 | import generalAnalysis from "./assets/demo/binsider-general-analysis.gif"; 6 | import staticAnalysis from "./assets/demo/binsider-static-analysis.gif"; 7 | import dynamicAnalysis from "./assets/demo/binsider-dynamic-analysis.gif"; 8 | import strings from "./assets/demo/binsider-strings.gif"; 9 | import hexdump from "./assets/demo/binsider-hexdump.gif"; 10 | 11 | export const images = atom([ 12 | { 13 | src: generalAnalysis, 14 | alt: "General Analysis", 15 | text: '"Get inside of the ELF binaries."', 16 | description: 17 | "Binsider offers powerful static and dynamic analysis tools, similar to readelf(1) and strace(1). It lets you inspect strings, examine linked libraries, and perform hexdumps, all within a user-friendly TUI.", 18 | }, 19 | { 20 | src: staticAnalysis, 21 | alt: "Static Analysis", 22 | text: '"Static analysis with a breeze."', 23 | description: 24 | "It helps you thoroughly examine the ELF file layout, including headers, sections, and symbols. It also supports precise searches across these components.", 25 | }, 26 | { 27 | src: dynamicAnalysis, 28 | alt: "Dynamic Analysis", 29 | text: '"It is strace(1) but better."', 30 | description: 31 | "You can trace system calls and signals by running the executable. It also offers in-depth insights to further understand the program's behavior and interactions with the system.", 32 | }, 33 | { 34 | src: strings, 35 | alt: "Strings", 36 | text: '"Strings are always interesting."', 37 | description: 38 | "It searches for sequences of printable characters in binaries and supports searching within those sequences for discovering specific patterns or data.", 39 | }, 40 | { 41 | src: hexdump, 42 | alt: "Hexdump", 43 | text: '"Good ol\' hexdump."', 44 | description: 45 | "It provides detailed hexdump analysis, allowing you to view and modify binary data in a readable hexadecimal or ASCII format.", 46 | }, 47 | ]); 48 | -------------------------------------------------------------------------------- /website/src/styles/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sl-font: "DejaVu Sans"; 3 | --sl-color-accent-low: #2e2c2b; 4 | --sl-color-accent: #7aa89f; 5 | --sl-color-accent-high: #e6c384; 6 | --sl-color-white: #dcd7ba; 7 | --sl-color-gray-1: #e8e6e3; 8 | --sl-color-gray-2: #cfcfcf; 9 | --sl-color-gray-3: #888787; 10 | --sl-color-gray-4: #6f6f6f; 11 | --sl-color-gray-5: #4a4846; 12 | --sl-color-gray-6: #1f1f1f; 13 | --sl-color-black: #161616; 14 | } 15 | 16 | :root[data-theme="light"] { 17 | --sl-font: "DejaVu Sans"; 18 | --sl-color-accent-low: #d4d2d0; 19 | --sl-color-accent: #4f7166; 20 | --sl-color-accent-high: #b58f5e; 21 | --sl-color-white: #2f2b23; 22 | --sl-color-gray-1: #1f1f1f; 23 | --sl-color-gray-2: #383838; 24 | --sl-color-gray-3: #777777; 25 | --sl-color-gray-4: #909090; 26 | --sl-color-gray-5: #b5b5b5; 27 | --sl-color-gray-6: #e0e0e0; 28 | --sl-color-black: #ededed; 29 | } 30 | 31 | .a { 32 | color: var(--sl-color-accent-high); 33 | text-decoration: none; 34 | } 35 | 36 | .a:hover { 37 | text-decoration: underline; 38 | } 39 | 40 | .hero { 41 | grid-template-columns: 1fr; 42 | padding: 1rem; 43 | } 44 | 45 | .hero .copy { 46 | display: none; 47 | } 48 | 49 | .hero .actions { 50 | justify-content: center; 51 | } 52 | 53 | h1[data-page-title], 54 | a.site-title { 55 | font-family: var(--__sl-font-mono); 56 | font-weight: bold; 57 | color: var(--color-brand); 58 | } 59 | 60 | .demo-image { 61 | margin: 0; 62 | max-width: 100%; 63 | height: auto; 64 | } 65 | 66 | [data-theme="light"] .demo-image { 67 | filter: invert(1); 68 | } 69 | 70 | [data-theme="light"] .hero-link { 71 | filter: invert(1); 72 | } 73 | 74 | [data-theme="light"] img[loading="lazy"] { 75 | filter: invert(1); 76 | } 77 | 78 | .action { 79 | transition: color 0.2s ease, background-color 0.2s ease; 80 | } 81 | 82 | .action:hover { 83 | color: var(--sl-color-accent-high); 84 | } 85 | 86 | .action.primary:hover { 87 | color: var(--sl-color-black); 88 | background-color: var(--sl-color-white); 89 | } 90 | 91 | kbd { 92 | color: var(--sl-color-accent-high); 93 | background-color: var(--sl-color-gray-6); 94 | padding: 0.2em 0.4em; 95 | border-radius: 4px; 96 | } 97 | 98 | strong { 99 | text-shadow: 0 0 5px var(--sl-color-gray-3); 100 | } 101 | -------------------------------------------------------------------------------- /website/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /website/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | import starlightPlugin from "@astrojs/starlight-tailwind"; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [starlightPlugin()], 10 | }; 11 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict" 3 | } --------------------------------------------------------------------------------