elf
Read Linux/Unix executables and shared libraries (ELF) without running them: architecture, type, entry point, sections and segments with entropy and permissions, the libraries it needs, imported and exported symbols, the program interpreter, and suspicious indicators (writable+executable memory, UPX section names, ptrace/dlopen imports, odd interpreters). Pure Python; works on bytes in the playground.
import elf
Example
import binary
import elf
# A tiny 64-bit Linux program, built byte by byte: the ELF header, one
# program header (a loadable, runnable segment) and ten bytes of code.
let data = binary.concat(
b"\x7fELF",
binary.pack("<BBBB", 2, 1, 1, 0),
binary.zeros(8),
binary.pack("<HHIQQQIHHHHHH", 2, 62, 1, 0x401000, 64, 0, 0, 64, 56, 1, 64, 0, 0),
binary.pack("<IIQQQQQQ", 1, 5, 0, 0x400000, 0x400000, 130, 130, 0x1000),
b"\x48\x31\xff\xb8\x3c\x00\x00\x00\x0f\x05",
)
let info = elf.parse_bytes(data)
say(f"{info.format} {info.type} for {info.architecture}, entry {info.entry_hex}")
for seg in info.segments {
say(f" segment {seg.type}: {seg.file_size} bytes, {seg.flags.join("+")}")
}
for hit in elf.indicators(info) {
say(f" [{hit.level}] {hit.detail}")
}
Functions
parse(path)
returns mapEverything elf knows about a file. Fails if the file isn't ELF.
Fields
"format"elf32 or elf64."architecture"the CPU type, like "x64" or "arm64"."machine"the CPU's longer name, like "x86-64"."type"executable, shared_object, relocatable or core.- "os_abi", "endian"
"is_pie", "is_static", "is_stripped"true or false."entry_point"the address where the program starts."entry_hex"the same address in hex, like "0x401000"."entry_section"the section the entry point is in."interpreter"the loader the program asks for."needed"the libraries it needs.- "soname", "rpath"
"sections"a list of maps with name, type, flags, address, offset, size, entropy, writable and executable.- "segments", "imports", "exports"
"version_needs"the symbol versions it needs per library, like {"libc.so.6": ["GLIBC_2.34"]}."version_definitions"the versions a library defines for others."symbol_versions"dynamic symbol -> version, like {"puts": "GLIBC_2.2.5"}."hardening"the checksec report: relro (full, partial or none), bind_now, nx, pie, canary, fortify, rpath and runpath.- "entropy", "size"
"truncated"true if the file is cut off."indicators"a list of {level, name, detail} findings.
Visualizehow elf.parse works
elf.parse("demo") gives {"format": "elf64", "bits": 64, "endian": "little", "architecture": "x64", "machine": "x8…
Real result: worked out by running this example with TB while the site was built.
parse_bytes(data)
returns mapLike elf.parse() but for bytes already in memory.
Visualizehow elf.parse_bytes works
elf.parse_bytes(bin) gives {"format": "elf64", "bits": 64, "endian": "little", "architecture": "x64", "machine": "x8…
Real result: worked out by running this example with TB while the site was built.
is_elf(path)
returns boolTrue if the file starts with the ELF magic bytes.
Visualizehow elf.is_elf works
elf.is_elf("demo") gives trueelf.is_elf("notes.txt") gives false
Real result: worked out by running this example with TB while the site was built.
sections(path)
returns listJust the sections: name, type, address, size, entropy and flags.
Visualizehow elf.sections works
elf.sections("demo").filter(s => s.name != "") gives [{"name": ".interp", "type": "progbits", "flags": ["alloc"], "address": 4194592, "offset"…
Real result: worked out by running this example with TB while the site was built.
segments(path)
returns listThe program headers (LOAD, INTERP, DYNAMIC...) with permissions.
Visualizehow elf.segments works
elf.segments("demo") gives [{"type": "load", "flags": ["read", "write", "exec"], "offset": 0, "address": 4194304, "f…
Real result: worked out by running this example with TB while the site was built.
imports(path)
returns listUndefined dynamic symbols: the functions it gets from shared libraries.
Visualizehow elf.imports works
elf.imports("demo") gives ["connect", "execve", "puts"]
Real result: worked out by running this example with TB while the site was built.
exports(path)
returns listGlobal symbols the file defines for others (what a .so offers).
Visualizehow elf.exports works
elf.exports("libdemo.so") gives ["plugin_init", "plugin_run"]elf.exports("demo") gives []
Real result: worked out by running this example with TB while the site was built.
needed(path)
returns listThe shared libraries it needs (DT_NEEDED), like ["libc.so.6"].
Visualizehow elf.needed works
elf.needed("demo") gives ["libc.so.6", "libssl.so.3"]
Real result: worked out by running this example with TB while the site was built.
indicators(path_or_info)
returns listSuspicious indicators for a file (or for the map elf.parse() gives back): [{"level": high/medium/low, "name", "detail"}].
Visualizehow elf.indicators works
elf.indicators("demo") gives [{"level": "high", "name": "writable_executable_segment", "detail": "A LOAD segment at 0x…
Real result: worked out by running this example with TB while the site was built.
summary(path)
returns textA short description, like "elf64 x64 executable (PIE), 29 sections, needs 1 library, 12 imports, stripped".
Visualizehow elf.summary works
elf.summary("demo") gives "elf64 x64 executable, 11 sections, needs 2 libraries, 3 imports, not stripped, 1 high-ri…
Real result: worked out by running this example with TB while the site was built.