Binkit is a Rust command-line toolbox for inspecting, disassembling, and modifying ELF64 binaries. It can display ELF metadata, disassemble x86-64 code, calculate an injection plan, append a payload, and update the executable entry point.
Use Binkit only on binaries you own or are authorized to modify. You are responsible for complying with applicable laws, licenses, and policies.
- Display ELF64, program-header, and section-header information for the supported format subset.
- Disassemble ELF sections or raw x86-64 machine code using Intel syntax.
- Calculate aligned payload addresses and signed
rel32return offsets. - Append a payload by repurposing a compatible section and program header.
- Update the ELF entry point.
- Protect existing output files unless
--forceis explicitly provided. - Write files atomically while preserving the source permissions.
- Reject malformed or unsupported ELF structures with descriptive errors.
See ELF compatibility for the exact supported formats and current limitations.
Binkit's iterative Capstone decoder outperformed GNU objdump on all three .text fixtures in
the reference benchmark:
- 26 KiB ELF: 2.00 ms versus
objdumpat 2.42 ms (1.21x faster). - 139 KiB ELF: 8.57 ms versus
objdumpat 13.37 ms (1.56x faster). - 7.65 MiB ELF: 261.48 ms versus
objdumpat 464.37 ms (1.78x faster).
Section listing remains faster in GNU readelf: memory-mapped Binkit took 0.83–1.13 ms,
compared with 0.46–0.51 ms for readelf. The large disassembly's peak memory use fell from
about 1.9 GiB in the original implementation to about 10 MiB with iterative decoding and mmap.
These are warm-cache medians from 15 measured runs after 3 warm-ups on Linux x86-64 with GNU binutils 2.42. Lower is better. See the full methodology and reproducible benchmark.
cargo install --locked binkitgit clone https://github.com/matheus-git/binkit.git
cd binkit
cargo build --release --locked
./target/release/binkit --helpPrebuilt binaries may also be available on the GitHub Releases page.
Run binkit --help or binkit <COMMAND> --help for the complete CLI reference.
The info command displays the complete ELF header and its program or section tables. Options
can be combined:
binkit info ./program --header
binkit info ./program --programs
binkit info ./program --sections
binkit info ./program --header --programs --sectionsThe header output includes identification fields, byte order, ABI, file type, architecture, entry point, flags, table offsets, entry sizes, counts, and the section-name table index.
Disassemble .text or another section from a little-endian x86-64 ELF file:
binkit disasm ./program
binkit disasm ./program --section .init
binkit disasm ./program --address 0x401000 --count 20
binkit disasm ./program --section .text --offset 64 --bytes 256Disassemble a file containing raw x86-64 machine code:
binkit disasm ./payload.bin --bin
binkit disasm ./payload.bin --bin --offset 16 --count 10--offset starts at a byte offset relative to the selected section or raw input, while
--address starts at a hexadecimal virtual address inside an ELF section. The two options are
mutually exclusive. --bytes limits the input window and --count limits decoded instructions;
when combined, disassembly stops at whichever limit is reached first.
Disassembly is streamed in aligned address, raw-byte, and assembly columns in a terminal. Pipes and redirected output use compact separators to avoid emitting unnecessary padding:
Address │ Bytes │ Assembly
───────────────────┼──────────────────────────────────────────────┼─────────────────────────────────
0x0000000000401000 │ 55 │ push rbp
0x0000000000401001 │ 48 89 E5 │ mov rbp, rsp
Calculate the aligned injection address and the signed distance from the start of the payload back to the current entry point:
binkit check-inject ./program
binkit check-inject ./program --return-address 0x401000This command only reports values; it does not modify or create a file.
The reported start-to-return distance is not a jmp rel32 immediate by itself. A jmp rel32
uses the address immediately after the jump instruction as its reference point.
Append a payload and write the modified ELF to a new file:
binkit inject ./program \
--inject ./payload.bin \
--set-entry \
--output ./program.injected--set-entry makes the injected payload the ELF entry point in the same operation. Omit it when
the payload should only be appended and mapped. The injection address is selected automatically;
--address is only an optional override.
By default, Binkit repurposes .note.gnu.property, renames it to .injected, and assigns an
aligned virtual address. Select another compatible section or address when needed:
binkit inject ./program \
--inject ./payload.bin \
--section .custom-note \
--address 0x405000 \
--return-address 0x401000 \
--output ./program.injectedInjection requires the selected section to have a matching program header and enough space in
the section-name string table for .injected. The command fails without creating the output if
these requirements are not met.
Existing destinations are protected. Use --force only when replacement is intentional:
binkit inject ./program -i ./payload.bin -o ./program.injected --forceThe payload is appended as provided. Binkit reports the injection address and return displacement but does not generate a jump, trampoline, or architecture-specific payload instructions.
Write a new ELF entry point to a separate output file:
binkit update ./program --entry 0x405000 --output ./program.updatedOmitting --output updates the input path atomically. Replacing a different existing destination
requires --force.
The ELF64 parser and core calculations are available as a Rust library:
use binkit::elf64::{Elf64Binary, calculate_rel32};
let bytes = std::fs::read("program")?;
let binary = Elf64Binary::new(&bytes)?;
println!("Entry point: 0x{:X}", binary.entry());
println!("rel32: {}", calculate_rel32(0x405000, binary.entry())?);
# Ok::<(), anyhow::Error>(())The project toolchain is pinned in rust-toolchain.toml and is installed automatically by
rustup. Run the same quality checks used by CI:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo build --release --lockedThe integration tests also use GNU binutils, GCC, Clang, and lld. They compile several ELF64 variants, execute an injected x86-64 payload, and verify that another payload can restore the initial process state and return to the original entry point. On Debian or Ubuntu:
sudo apt-get update
sudo apt-get install binutils build-essential clang lldThe fuzz target requires nightly Rust and cargo-fuzz:
cargo install cargo-fuzz --locked
cargo +nightly fuzz run elf64-roundtripGenerated fuzz corpora, artifacts, and build output are ignored by Git.
Contributions should include tests for behavior changes and pass the formatting, lint, test, and release-build commands above. Useful areas include additional ELF validation, executable injection fixtures, architecture support, structured output, and other binary formats.
Licensed under the MIT License.