The elf2bin converter produces flat binary images from static-pie ELF executables. The output is designed for minimal stager loading — no relocation processing, no symbol resolution, just mmap + memcpy + jump.
A static-pie ELF built with musl is fully self-relocating: its _dlstart_c entry point walks its own PT_DYNAMIC and applies relocations internally. This means the loader (stager) doesn't need to understand relocations at all — it just needs to:
- Map enough memory (file data + BSS)
- Copy the image
- Build a minimal stack (argc, argv, envp, auxv)
- Jump to the entry point
The output is a contiguous flat memory image indexed by virtual address. The ELF header is preserved at offset 0 so the loader can read:
e_entry— entry point offset from basee_phoff,e_phentsize,e_phnum— program headers (for auxv)
By default, elf2bin trims trailing BSS (zero-initialized data) from the output. The image only extends to the last byte of actual file data (p_filesz), not the full memory size (p_memsz). The loader's mmap(MAP_ANONYMOUS) zero-fills beyond, providing the BSS region automatically.
This significantly reduces wire size — a typical musl static-pie binary has 20-100KB of BSS that becomes zero bytes on the wire.
Runtime-dead sections are zeroed for better compressibility:
.symtab,.strtab,.shstrtab,.comment— not needed at runtime.note.*sections — not needed at runtime- Section header table — zeroed and e_shoff/e_shnum cleared in ELF header
The .dynstr section is preserved (needed by the self-relocator via DT_STRTAB).
# Convert with default optimizations (BSS trim + metadata strip)
python3 tools/lltool.py elf2bin input.elf output.bin
# Print entry offset only
python3 tools/lltool.py elf2bin -e input.elf
# Keep dead metadata and trailing BSS (larger output, for debugging)
python3 tools/lltool.py elf2bin --no-strip input.elf output.bin
# Append legacy bin_info trailer
python3 tools/lltool.py elf2bin --trailer input.elf output.binThe minimal loading sequence:
mmap(NULL, page_align(memsz), PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)
recv(socket, base, file_len) // or read from any source
jump(base + e_entry, stack)
Where memsz is computed from LOAD segments:
for each PT_LOAD segment:
memsz = max(memsz, p_vaddr + p_memsz)
memsz = page_align(memsz)
The entry point expects a System V initial stack:
[sp + 0] argc
[sp + 8] argv[0] ... argv[argc-1]
NULL
envp[0] ... envp[n-1]
NULL
AT_BASE base_address
AT_PHDR base + e_phoff
AT_PHENT e_phentsize
AT_PHNUM e_phnum
AT_PAGESZ 0x1000
AT_RANDOM base (or any 16 random bytes)
AT_NULL 0
AT_BASE is required — musl's _dlstart_c uses it as the relocation base.
libload provides two functions for loading flat binary images directly:
pid_t libload_exec_bin(const unsigned char *buf, size_t len,
char *const argv[], char *const envp[]);
int libload_run_bin(const unsigned char *buf, size_t len,
char *const argv[], char *const envp[]);These work identically to libload_exec / libload_run but for flat binary images instead of full ELF executables. The image must start with a valid ELF header (preserved by elf2bin).
#include <libload.h>
#include <sys/wait.h>
/* buf contains the flat binary produced by lltool elf2bin */
char *argv[] = { "program", NULL };
pid_t pid = libload_exec_bin(buf, len, argv, NULL);
if (pid > 0) {
int status;
waitpid(pid, &status, 0);
}The input ELF must be a static-pie executable. With musl:
musl-gcc -static-pie -o program program.cOr with a musl cross-compiler:
aarch64-linux-musl-gcc -static-pie -o program program.cThe musl runtime handles all self-relocation internally via _dlstart_c, making the resulting binary fully position-independent with no external dependencies.