caver 0.2.0

ELF64 code cave injection library
Documentation

caver

Crates.io docs.rs License: MIT

ELF64 code cave injection library for Rust.

caver creates executable code caves in ELF64 binaries by appending a new loadable segment. This is useful when a binary has little or no natural slack space for patching or instrumentation.

The injected cave is exported as a symbol, allowing reverse-engineering tools such as Binary Ninja, Ghidra, and IDA Pro to automatically detect it.

The library focuses only on structural ELF modification (creating space for new code). Assembly payloads, hooks, and patching are intended to be written inside a disassembler after injection.

Goals

caver is intentionally small in scope. It aims to:

  • Create executable code caves in binaries that lack natural slack space
  • Append a new executable PT_LOAD segment safely
  • Expose the cave as a symbol for easy discovery in disassemblers
  • Provide utilities for inspecting ELF layout and locating existing caves

It does not attempt to:

  • assemble payloads
  • generate trampolines
  • automatically hook functions
  • patch instructions

These tasks are better handled inside reverse-engineering tools.

Supported Architectures

  • x86_64
  • AArch64
  • RISC-V 64

All architectures require ELF64 little-endian binaries.

Install

[dependencies]
caver = "0.2"

Usage

Inject caves

name is the ELF section name and must start with .. symbol is the exported symbol name visible in disassemblers — if not set, caver derives one automatically from the section name.

use caver::cave::{CaveOptions, FillByte, inject, inject_many};
use caver::elf::ElfFile;

fn main() -> caver::error::Result<()> {
    let elf = ElfFile::open("./binary")?;

    // single cave — auto-generated symbol name
    // default fill byte = ArchNop
    // size() is required
    // name() is required
    // symbol() is optional
    let patched = inject(
        &elf,
        &CaveOptions::builder()
            .size(512)
            .name(".mycode")
            .build()?,
    )?;

    println!("{}", patched.info());
    patched.write("./binary_patched")?;

    // multiple caves — mix of auto and custom symbol names
    let patched = inject_many(&elf, &[
        CaveOptions::builder()
            .size(512)
            .name(".mycode")
            .build()?,
        CaveOptions::builder()
            .size(256)
            .name(".mydata")
            .fill(FillByte::Zero) // data cave
            .symbol("my_cool_data")
            .build()?,
        CaveOptions::builder()
            .size(128)
            .name(".hook")
            .symbol("my_hook")
            .build()?,
    ])?;

    for info in patched.infos() {
        println!("{info}");
    }

    patched.write("./binary_patched")?;

    Ok(())
}

Inspect an existing binary

use caver::elf::ElfFile;

fn main() -> caver::error::Result<()> {
    let elf = ElfFile::open("./binary")?;

    // find executable sections
    for s in elf.sections()? {
        if s.is_executable() {
            println!("executable section: {}", s.name);
        }
    }

    // find writable segments
    for s in elf.segments()? {
        if s.is_writable() {
            println!("writable segment at {:#x}", s.vma);
        }
    }

    // find existing unused regions
    for cave in elf.find_caves(64)? {
        if cave.is_executable() {
            println!("executable cave: {cave}");
        }
    }

    Ok(())
}

Cave Symbols

The exported symbol name is derived automatically from the section name and fill pattern:

Fill Auto symbol Disassembler treatment
FillByte::ArchNop caverfn_<name> function entry point
FillByte::Zero caverobj_<name> data variable

For example, injecting .mycode with FillByte::ArchNop produces caverfn_mycode, which most disassemblers will automatically treat as a function. Injecting .mydata with FillByte::Zero produces caverobj_mydata, treated as a data variable.

Use .symbol("my_name") in the builder to override the auto-generated name entirely.

Note that name and symbol serve different purposes — name is the internal ELF section name used to identify the cave structurally, while symbol is the exported name visible to disassemblers and tooling.

Finding Natural Caves

The find_caves function scans the binary for runs of 0x00 or 0x90 bytes that meet a minimum size threshold. Results are sorted largest first. This is useful for locating existing slack space before deciding whether to inject a new segment at all.

License

MIT