diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..92fb72a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a7a75e9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: rsd CI + +on: + push: + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + +jobs: + rust-ci: + runs-on: ubuntu-latest + steps: + - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + azure.archive.ubuntu.com:80 + esm.ubuntu.com:443 + github.com:443 + motd.ubuntu.com:443 + packages.microsoft.com:443 + sh.rustup.rs:443 + static.rust-lang.org:443 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - run: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + export PATH="$HOME/.cargo/bin:$PATH" + rustup default stable + rustup component add rustfmt + - name: Run Checks + run: | + cargo fmt --all -- --check + cargo build --all --release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6eb42e0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: rsd release + +on: + push: + tags: + - '*' + +permissions: + actions: read + id-token: write + contents: write + +jobs: + build: + runs-on: ubuntu-latest + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + steps: + - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + azure.archive.ubuntu.com:80 + esm.ubuntu.com:443 + github.com:443 + motd.ubuntu.com:443 + objects.githubusercontent.com:443 + packages.microsoft.com:443 + raw.githubusercontent.com:443 + sh.rustup.rs:443 + static.rust-lang.org:443 + uploads.github.com:443 + - name: Checkout repository + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - run: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + export PATH="$HOME/.cargo/bin:$PATH" + rustup default stable + - name: Build using cargo + run: | + cargo build --release + cp target/release/rsd . + - name: Generate subject + id: hash + run: | + set -euo pipefail + echo "hashes=$(sha256sum ./rsd | base64 -w0)" >> "$GITHUB_OUTPUT" + - name: Generate SBOM via Syft + uses: anchore/sbom-action@e8d2a6937ecead383dfe75190d104edd1f9c5751 + provenance: + needs: + - build + # https://github.com/slsa-framework/slsa-github-generator/issues/722 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: "${{ needs.build.outputs.hashes }}" + upload-assets: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5f2887 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Binaries +rsd diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..93bdb61 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rsd" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a342d81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM cgr.dev/chainguard/rust as build + +WORKDIR /build + +COPY . . + +RUN make build + +FROM cgr.dev/chainguard/static + +COPY --from=build /build/rsd /rsd + +ENTRYPOINT ["/rsd"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0e8e814 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: build + +build: + rustc -C target-feature=+crt-static src/main.rs -o rsd + +docker: + docker buildx build -t rsd:latest . + +fmt: + cargo fmt --all + +fmt-check: + cargo fmt --all -- --check + +test: + cargo test --all --release + +release: + cargo build --all --release + +sbom: + syft -o spdx-json . | jq . > sbom.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..90f9372 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# rsd + +`rsd` Rust implemention of something resembling `xxd -e -l 64`. Its functionality is limited to looking at the headers of ELF binaries and outputting the details in a mostly- human-readable format. + +## Why? + +I wanted to learn Rust a little better; I also wanted a more readable version of `xxd -e -l 64` when parsing ELF headers rather than parsing something like this: +``` +ced27abc2bef:/# xxd -e -l 64 /bin/sh +00000000: 464c457f 00010102 00000000 00000000 .ELF............ +00000010: 00b70003 00000001 0000a780 00000000 ................ +00000020: 00000040 00000000 000a0348 00000000 @.......H....... +00000030: 00000000 00380040 00400009 00180019 ....@.8...@..... +``` +or this: +``` +ced27abc2bef:/# xxd -l 64 /bin/sh +00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ +00000010: 0300 b700 0100 0000 80a7 0000 0000 0000 ................ +00000020: 4000 0000 0000 0000 4803 0a00 0000 0000 @.......H....... +00000030: 0000 0000 4000 3800 0900 4000 1900 1800 ....@.8...@..... +``` + +I've been writing YARA rules recently and knowing how to locate information like this can prove useful, though making it more human-readable is more efficient as well. + +## What works for now? + +- Building locally via `rustc` or via Dockerfile +- ELF binaries + +## Usage + +`./rsd ` + +Example (run from Wolfi): +``` +./rsd /bin/sh +Full header: +7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 03 00 B7 00 01 00 00 00 80 A7 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 03 0A 00 00 00 00 00 00 00 00 00 40 00 38 00 09 00 40 00 19 00 18 00 ELF File Type: Shared (0x03) +Machine Type: AArch64 (0x00B7) + +ELF Class: 64-bit +Data Encoding: Little-endian +ELF Version: 1 +Entry Point Address: 42880 +Program Header Table Offset: 64 +Section Header Table Offset: 656200 +ELF Header Size: 64 bytes +Program Header Table Entry Size: 56 bytes +Number of Program Header Table Entries: 9 +Section Header Table Entry Size: 64 bytes +Number of Section Header Table Entries: 25 +Section Header String Table Index: 24 + +Segment Information: +Segment 0: + Type: PT_NOTE (0x00000004) + Offset: 64 + Virtual Address: 64 + Physical Address: 64 + File Size: 0x00000000000001F8 (504 bytes) + Memory Size: 0x00000000000001F8 (504 bytes) + Flags: Unknown (0x00000008) + +Segment 1: + Type: PT_NOTE (0x00000004) + Offset: 568 + Virtual Address: 568 + Physical Address: 568 + File Size: 0x000000000000001B (27 bytes) + Memory Size: 0x000000000000001B (27 bytes) + Flags: R (0x00000001) + +Segment 2: + Type: PT_SHLIB (0x00000005) + Offset: 0 + Virtual Address: 0 + Physical Address: 0 + File Size: 0x000000000008F118 (586008 bytes) + Memory Size: 0x000000000008F118 (586008 bytes) + Flags: Unknown (0x00010000) + +Segment 3: + Type: PT_PHDR (0x00000006) + Offset: 646864 + Virtual Address: 646864 + Physical Address: 646864 + File Size: 0x0000000000002399 (9113 bytes) + Memory Size: 0x0000000000002A00 (10752 bytes) + Flags: Unknown (0x00010000) + +Segment 4: + Type: PT_PHDR (0x00000006) + Offset: 651720 + Virtual Address: 651720 + Physical Address: 651720 + File Size: 0x0000000000000220 (544 bytes) + Memory Size: 0x0000000000000220 (544 bytes) + Flags: Unknown (0x00000008) + +Segment 5: + Type: PT_NOTE (0x00000004) + Offset: 596 + Virtual Address: 596 + Physical Address: 596 + File Size: 0x0000000000000020 (32 bytes) + Memory Size: 0x0000000000000020 (32 bytes) + Flags: X (0x00000004) + +Segment 6: + Type: PT_NOTE (0x00000004) + Offset: 585820 + Virtual Address: 585820 + Physical Address: 585820 + File Size: 0x0000000000000034 (52 bytes) + Memory Size: 0x0000000000000034 (52 bytes) + Flags: X (0x00000004) + +Segment 7: + Type: PT_PHDR (0x00000006) + Offset: 0 + Virtual Address: 0 + Physical Address: 0 + File Size: 0x0000000000000000 (0 bytes) + Memory Size: 0x0000000000000000 (0 bytes) + Flags: Unknown (0x00000010) + +Segment 8: + Type: PT_NOTE (0x00000004) + Offset: 646864 + Virtual Address: 646864 + Physical Address: 646864 + File Size: 0x0000000000002130 (8496 bytes) + Memory Size: 0x0000000000002130 (8496 bytes) + Flags: R (0x00000001) +``` + +Running `rsd` from MacOS will result in this: +``` +❯ ./rsd /bin/sh +/bin/sh is not an ELF file (CAFEBABE). +``` + +## Will anything be added to this project? + +Maybe -- I want to start automating various things in whatever language seems right. + +Analyizing Mach-O binaries (i.e., MacOS binaries) doesn't seem to be as common but would be something to support in a future PR. diff --git a/sbom.json b/sbom.json new file mode 100644 index 0000000..aef3616 --- /dev/null +++ b/sbom.json @@ -0,0 +1,216 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": ".", + "documentNamespace": "https://anchore.com/syft/dir/cb60b504-69c4-47c2-a395-31781cdf2202", + "creationInfo": { + "licenseListVersion": "3.24", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.5.0" + ], + "created": "2024-06-09T01:04:17Z" + }, + "packages": [ + { + "name": "actions/checkout", + "SPDXID": "SPDXRef-Package-github-action-actions-checkout-842fad84e08d9396", + "versionInfo": "a5ac7e51b41094c92402da3b24376905380afc29", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from GitHub Actions workflow file or composite action file: /.github/workflows/ci.yml", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:actions\\/checkout:actions\\/checkout:a5ac7e51b41094c92402da3b24376905380afc29:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:github/actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29" + } + ] + }, + { + "name": "rsd", + "SPDXID": "SPDXRef-Package-rust-crate-rsd-fa2db6addbf12545", + "versionInfo": "0.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from rust cargo manifest: /Cargo.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:rsd:rsd:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:cargo/rsd@0.1.0" + } + ] + }, + { + "name": "step-security/harden-runner", + "SPDXID": "SPDXRef-Package-github-action-step-security-harden-runner-1795139107f2d9bf", + "versionInfo": "17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from GitHub Actions workflow file or composite action file: /.github/workflows/ci.yml", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step-security\\/harden-runner:step-security\\/harden-runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step-security\\/harden-runner:step_security\\/harden_runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step_security\\/harden_runner:step-security\\/harden-runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step_security\\/harden_runner:step_security\\/harden_runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step-security\\/harden:step-security\\/harden-runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step-security\\/harden:step_security\\/harden_runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step_security\\/harden:step-security\\/harden-runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step_security\\/harden:step_security\\/harden_runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step:step-security\\/harden-runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:step:step_security\\/harden_runner:17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:github/step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6" + } + ] + }, + { + "name": ".", + "SPDXID": "SPDXRef-DocumentRoot-Directory-.", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "/.github/workflows/ci.yml", + "SPDXID": "SPDXRef-File-.github-workflows-ci.yml-7561d461b00ff11d", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "" + }, + { + "fileName": "/Cargo.lock", + "SPDXID": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0000000000000000000000000000000000000000" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-github-action-step-security-harden-runner-1795139107f2d9bf", + "relatedSpdxElement": "SPDXRef-File-.github-workflows-ci.yml-7561d461b00ff11d", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-github-action-actions-checkout-842fad84e08d9396", + "relatedSpdxElement": "SPDXRef-File-.github-workflows-ci.yml-7561d461b00ff11d", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-rust-crate-rsd-fa2db6addbf12545", + "relatedSpdxElement": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-.", + "relatedSpdxElement": "SPDXRef-Package-github-action-actions-checkout-842fad84e08d9396", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-.", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-rsd-fa2db6addbf12545", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-.", + "relatedSpdxElement": "SPDXRef-Package-github-action-step-security-harden-runner-1795139107f2d9bf", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-.", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..08217ba --- /dev/null +++ b/src/display.rs @@ -0,0 +1,9 @@ +use std::fmt; + +pub struct HexAddress(pub u64); + +impl fmt::Display for HexAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x{:016X} ({} bytes)", self.0, self.0) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9780dcc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,377 @@ +use std::env; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::process; + +mod display; + +const ELF_HEADER_SIZE: usize = 64; +const ELF_MAGIC: [u8; 4] = [0x7F, 0x45, 0x4C, 0x46]; +const PHDR_ENTRY_SIZE: usize = 56; + +fn main() { + // Get the command-line arguments + let args: Vec = env::args().collect(); + + // Check if the program name is provided + if args.len() != 2 { + println!("Usage: {} ", args[0]); + return; + } + + // Open the file + let mut file = get_file(); + + // Read the ELF header + let mut header = [0u8; ELF_HEADER_SIZE]; + if let Err(e) = file.read_exact(&mut header) { + println!("Failed to read the ELF header: {}", e); + return; + } + + let magic = &header[..4]; + let magic_string = magic + .iter() + .map(|b| format!("{:02X}", b)) + .collect::(); + + // Check if the file is an ELF file + if magic != ELF_MAGIC { + println!("{} is not an ELF file ({})", args[1], magic_string); + return; + } + + // Extract the ELF file type + let (elf_type_str, elf_type) = get_elf_type(&header); + + let (machine_type_str, machine_type) = get_machine_type(&header); + + // Extract the ELF class + let (elf_class_str, _) = get_elf_class(&header); + + // Extract the data encoding + let (data_encoding_str, _) = get_data_encoding(&header); + + // Extract the ELF version + let elf_version = get_elf_version(&header); + + // Extract the entry point address + let entry_point = get_entry_point(&header); + + // Extract the program header table offset + let phdr_offset = get_phdr_offset(&header); + + // Extract the section header table offset + let shdr_offset = get_shdr_offset(&header); + + // Write a function that takes two bytes and returns a u16 + // Extract the ELF header size + let elf_header_size = bits_to_u16(&[header[0x34], header[0x35]]); + + // Extract the program header table entry size + let phdr_entry_size = bits_to_u16(&[header[0x36], header[0x37]]); + + // Extract the number of program header table entries + let phdr_num_entries = bits_to_u16(&[header[0x38], header[0x39]]); + + // Extract the section header table entry size + let shdr_entry_size = bits_to_u16(&[header[0x3A], header[0x3B]]); + + // Extract the number of section header table entries + let shdr_num_entries = bits_to_u16(&[header[0x3C], header[0x3D]]); + + // Extract the section header string table index + let shdr_str_index = bits_to_u16(&[header[0x3E], header[0x3F]]); + + // Print the extracted information + println!("Full header:"); + for byte in &header[..ELF_HEADER_SIZE] { + print!("{:02X} ", byte); + } + println!("ELF File Type: {} (0x{:02X})", elf_type_str, elf_type); + println!( + "Machine Type: {} (0x{:04X})", + machine_type_str, machine_type + ); + println!(); + println!("ELF Class: {}", elf_class_str); + println!("Data Encoding: {}", data_encoding_str); + println!("ELF Version: {}", elf_version); + println!("Entry Point Address: {}", entry_point); + println!("Program Header Table Offset: {}", phdr_offset); + println!("Section Header Table Offset: {}", shdr_offset); + println!("ELF Header Size: {} bytes", elf_header_size); + println!("Program Header Table Entry Size: {} bytes", phdr_entry_size); + println!( + "Number of Program Header Table Entries: {}", + phdr_num_entries + ); + println!("Section Header Table Entry Size: {} bytes", shdr_entry_size); + println!( + "Number of Section Header Table Entries: {}", + shdr_num_entries + ); + println!("Section Header String Table Index: {}", shdr_str_index); + + // Extract the number of program header table entries + let phdr_num_entries = bits_to_u16(&[header[0x38], header[0x39]]); + + // Seek to the program header table offset + file.seek(SeekFrom::Start(phdr_offset)).unwrap(); + + // Print the segment information + println!("\nSegment Information:"); + for i in 0..phdr_num_entries { + let mut phdr_entry = [0u8; PHDR_ENTRY_SIZE]; + file.read_exact(&mut phdr_entry).unwrap(); + + let segment_type = get_phdr_segment_type(&phdr_entry); + let segment_offset = get_phdr_segment_offset(&phdr_entry); + let segment_vaddr = get_phdr_segment_vaddr(&phdr_entry); + let segment_paddr = get_phdr_segment_paddr(&phdr_entry); + let segment_filesz = get_phdr_segment_filesz(&phdr_entry); + let segment_memsz = get_phdr_segment_memsz(&phdr_entry); + let segment_flags = get_phdr_segment_flags(&phdr_entry); + + println!("Segment {}:", i); + println!( + " Type: {} (0x{:08X})", + get_segment_type(segment_type), + segment_type + ); + println!(" Offset: {}", segment_offset); + println!(" Virtual Address: {}", segment_vaddr); + println!(" Physical Address: {}", segment_paddr); + println!(" File Size: {}", segment_filesz); + println!(" Memory Size: {}", segment_memsz); + println!( + " Flags: {} (0x{:08X})", + get_segment_flags(segment_flags), + segment_flags + ); + println!(); + } +} + +fn get_entry_point(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x18], + header[0x19], + header[0x1A], + header[0x1B], + header[0x1C], + header[0x1D], + header[0x1E], + header[0x1F], + ]) +} + +fn get_phdr_offset(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x20], + header[0x21], + header[0x22], + header[0x23], + header[0x24], + header[0x25], + header[0x26], + header[0x27], + ]) +} + +fn get_shdr_offset(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x28], + header[0x29], + header[0x2A], + header[0x2B], + header[0x2C], + header[0x2D], + header[0x2E], + header[0x2F], + ]) +} + +fn get_phdr_segment_type(phdr_entry: &[u8]) -> u32 { + u32::from_le_bytes([ + phdr_entry[0x04], + phdr_entry[0x05], + phdr_entry[0x06], + phdr_entry[0x07], + ]) +} + +fn get_phdr_segment_offset(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x08], + phdr_entry[0x09], + phdr_entry[0x0A], + phdr_entry[0x0B], + phdr_entry[0x0C], + phdr_entry[0x0D], + phdr_entry[0x0E], + phdr_entry[0x0F], + ]) +} + +fn get_phdr_segment_vaddr(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x10], + phdr_entry[0x11], + phdr_entry[0x12], + phdr_entry[0x13], + phdr_entry[0x14], + phdr_entry[0x15], + phdr_entry[0x16], + phdr_entry[0x17], + ]) +} + +fn get_phdr_segment_paddr(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x18], + phdr_entry[0x19], + phdr_entry[0x1A], + phdr_entry[0x1B], + phdr_entry[0x1C], + phdr_entry[0x1D], + phdr_entry[0x1E], + phdr_entry[0x1F], + ]) +} + +fn get_phdr_segment_filesz(phdr_entry: &[u8]) -> display::HexAddress { + display::HexAddress(u64::from_le_bytes([ + phdr_entry[0x20], + phdr_entry[0x21], + phdr_entry[0x22], + phdr_entry[0x23], + phdr_entry[0x24], + phdr_entry[0x25], + phdr_entry[0x26], + phdr_entry[0x27], + ])) +} + +fn get_phdr_segment_memsz(phdr_entry: &[u8]) -> display::HexAddress { + display::HexAddress(u64::from_le_bytes([ + phdr_entry[0x28], + phdr_entry[0x29], + phdr_entry[0x2A], + phdr_entry[0x2B], + phdr_entry[0x2C], + phdr_entry[0x2D], + phdr_entry[0x2E], + phdr_entry[0x2F], + ])) +} + +fn get_phdr_segment_flags(phdr_entry: &[u8]) -> u32 { + u32::from_le_bytes([ + phdr_entry[0x30], + phdr_entry[0x31], + phdr_entry[0x32], + phdr_entry[0x33], + ]) +} + +fn get_file() -> File { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + process::exit(1); + } + + let file = File::open(&args[1]); + if file.is_err() { + eprintln!("Failed to open file: {}", args[1]); + process::exit(1); + } + + file.unwrap() +} + +fn get_segment_type(segment_type: u32) -> &'static str { + match segment_type { + 0x00000000 => "PT_NULL", + 0x00000001 => "PT_LOAD", + 0x00000002 => "PT_DYNAMIC", + 0x00000003 => "PT_INTERP", + 0x00000004 => "PT_NOTE", + 0x00000005 => "PT_SHLIB", + 0x00000006 => "PT_PHDR", + 0x00000007 => "PT_TLS", + 0x6474e550 => "PT_GNU_EH_FRAME", + 0x6474e551 => "PT_GNU_STACK", + 0x6474e552 => "PT_GNU_RELRO", + _ => "Unknown", + } +} + +fn get_segment_flags(segment_flags: u32) -> String { + let mut flags = String::new(); + match segment_flags { + 0x01 => flags.push_str("R"), + 0x02 => flags.push_str("W"), + 0x04 => flags.push_str("X"), + _ => flags.push_str("Unknown"), + } + flags +} + +fn get_machine_type(header: &[u8]) -> (String, u16) { + let machine_type = u16::from_le_bytes([header[0x12], header[0x13]]); + let machine_type_str = match machine_type { + 0x03 => "x86", + 0x3E => "x86-64", + 0xB7 => "AArch64", + _ => "Unknown", + } + .to_string(); + (machine_type_str, machine_type) +} + +fn get_elf_type(header: &[u8]) -> (String, u8) { + let elf_type = header[0x10]; + let elf_type_str = match elf_type { + 0x01 => "Relocatable", + 0x02 => "Executable", + 0x03 => "Shared", + 0x04 => "Core", + _ => "Unknown", + } + .to_string(); + (elf_type_str, elf_type) +} + +fn get_elf_class(header: &[u8]) -> (String, u8) { + let elf_class = header[0x04]; + let elf_class_str = match elf_class { + 1 => "32-bit", + 2 => "64-bit", + _ => "Unknown", + } + .to_string(); + (elf_class_str, elf_class) +} + +fn get_data_encoding(header: &[u8]) -> (String, u8) { + let data_encoding = header[0x05]; + let data_encoding_str = match data_encoding { + 1 => "Little-endian", + 2 => "Big-endian", + _ => "Unknown", + } + .to_string(); + (data_encoding_str, data_encoding) +} + +fn bits_to_u16(bits: &[u8; 2]) -> u16 { + let value = u16::from_le_bytes(*bits); + value +} + +fn get_elf_version(header: &[u8]) -> u8 { + let elf_version = header[0x06]; + elf_version +}