IR-first ELF patching in Rust.
A patchelf reimplementation that treats ELF patching like a compiler pass :)
... something something rewrite in rust ... something something proper IR lifting ... something something
hexforge patches ELF binaries and shared libraries (interpreter, rpath/runpath, needed libs, soname, execstack, symbol-version cleanup, dynamic symbol renames, and more).
The core idea is to treat patching as a compiler pipeline, not as byte surgery. hexforge first lifts raw ELF bytes into a typed intermediate representation (IR), checks that the representation is internally consistent, applies transforms against that IR, then checks again before emission. If any stage detects inconsistent structure, the pipeline stops.
The whole idea is to not treat the files as blobs but lifting them to the cozy world of abstractions. It models ELF structure explicitly: headers, program/section layout, dynamic table entries, dynstr/dynsym state, hash/version tables, and relocation-linked symbol references. Lifting normalizes those pieces into one coherent model so operations can update related structures together instead of patching one table and hoping the rest still agrees.
Validation enforces cross-structure invariants (for example, offsets and spans, dynamic tag consistency, dynsym/dynstr compatibility, and version table coherence). That gives transforms a clear contract: mutate only through validated IR, keep linked structures synchronized, and hand back a model that still passes global checks.
hexforge has two main subcommands:
inspect: read ELF metadata with strict parsing/validation.patch: apply mutations only when invariants hold end-to-end.
Examples:
# Inspect
hexforge inspect --interpreter --rpath --needed ./app
hexforge inspect --format json ./app
# Patch interpreter and rpath
hexforge patch --set-interpreter /lib64/ld-linux-x86-64.so.2 ./app
hexforge patch --set-rpath '/opt/lib:/usr/local/lib' --force-rpath ./app
# DT_NEEDED edits
hexforge patch --add-needed libfoo.so.1 --remove-needed libbar.so.0 ./app
hexforge patch --replace-needed libssl.so.1.1 libssl.so.3 ./app
# Write to a different output
hexforge patch --set-rpath /x ./app --output ./app.patchedPyO3 bindings are available in crates/hexforge-py.
import hexforge
info = hexforge.inspect_file("./app")
plan = hexforge.PatchPlan().set_rpath("/opt/lib", force_rpath=True)
outcome = hexforge.patch_file("./app", "./app.patched", plan)
print(outcome.changed)hexforge-elf: lift/check/emit + IR validation.hexforge-ops: deterministic mutation ops over IR.hexforge-model: inspection APIs.hexforge-mutate: typed patch API (PatchPlan).hexforge(CLI):inspectandpatch.hexforge-py: Python bindings.