caver 0.3.0

ELF64 code cave injection library
Documentation

caver

Crates.io docs.rs License: MIT

ELF64 code cave injection library for Rust.

caver creates code caves in ELF64 binaries by appending a new loadable segment. It handles only structural ELF modification — creating and labelling the space. Assembly, trampolines, and hooking are left to your disassembler.

Supports x86_64, AArch64, and RISC-V 64 (little-endian ELF64 only).

Install

[dependencies]
caver = "0.3"

Cave types

Every cave has a fill pattern that controls both its contents and how disassemblers interpret it:

Fill Contents ELF symbol type Disassembler sees
FillByte::ArchNop arch NOP sled STT_FUNC function entry point
FillByte::Zero null bytes STT_OBJECT data variable

Use ArchNop when you plan to write code into the cave. Use Zero when you need a writable data region.

Naming

Each cave gets two names that serve different purposes:

  • name — the ELF section name (e.g. .mycode). Must start with .. This is what readelf, objdump, and elf.sections() see.
  • symbol — the exported symbol name (e.g. caverfn_mycode). This is what Binary Ninja, Ghidra, and IDA see when they auto-analyse the binary.

If you don't call .symbol(), caver derives one from the section name: caverfn_<name> for NOP caves, caverobj_<name> for zero caves.

.mycode  →  caverfn_mycode   (ArchNop, auto)
.mydata  →  caverobj_mydata  (Zero, auto)
.hook    →  my_hook          (ArchNop, overridden)

Usage

Inject a single cave

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

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

    let patched = inject(
        &elf,
        &CaveOptions::builder()
            .size(512)
            .name(".mycode")
            // .symbol("my_name")  // optional override
            // .fill(FillByte::Zero)  // default is ArchNop
            .build()?,
    )?;

    // info() returns vma, offset, size, section name, and resolved symbol
    println!("{}", patched.info());
    patched.write("./binary_patched")?;

    Ok(())
}

Inject multiple caves

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

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

    let patched = inject_many(&elf, &[
        // NOP sled — auto symbol: caverfn_mycode
        CaveOptions::builder()
            .size(512)
            .name(".mycode")
            .build()?,
        // Data region — auto symbol: caverobj_mydata
        CaveOptions::builder()
            .size(256)
            .name(".mydata")
            .fill(FillByte::Zero)
            .build()?,
        // NOP sled — custom symbol overrides auto-generation
        CaveOptions::builder()
            .size(128)
            .name(".hook")
            .symbol("my_hook")
            .build()?,
    ])?;

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

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

    Ok(())
}

Inspect a binary

use caver::elf::ElfFile;

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

    // Scan for existing slack space before deciding to inject
    for cave in elf.find_caves(64)? {
        if cave.is_executable() {
            println!("existing executable cave: {cave}");
        }
    }

    Ok(())
}

License

MIT