Read ELF binaries in your browser or terminal: sections, symbols, relocations, dynamic dependencies and a PIE/NX/RELRO/canary security report — pure JavaScript, zero dependencies, nothing uploaded.
Drop a binary on the page and click a section to highlight it in the hex view; or run the CLI for the same report in your pipeline.
- ELF32 + ELF64, LSB and MSB, bounds-checked parsing with byte-precise errors on truncated/corrupt files
- sections, program headers,
.symtab/.dynsym,.rela/.relwith named x86-64/i386 relocation types,.dynamicwith resolved strings - dependency graph from
DT_NEEDED(+ RUNPATH/RPATH flags) - security report: PIE, NX, full/partial/none RELRO, stack canary, RPATH, stripped — the classic hardening checklist
- interactive hex view wired to the structure tables
- six sample binaries generated byte-by-byte by a from-scratch ELF
writer (
samples/tools/) — they double as the parser's test fixtures
git clone https://github.com/sudeanb/elf-scope.git
cd elf-scope
node --test # 16 tests
node bin/elfscope.js security samples/pie-full-relro-canary.elf$ node bin/elfscope.js security samples/pie-full-relro-canary.elf
⌘ samples/pie-full-relro-canary.elf — 4/6 hardening checks pass
✓ PIE ET_DYN + PT_INTERP → PIE executable
✓ NX PT_GNU_STACK flags RW — stack not executable
✓ RELRO PT_GNU_RELRO + BIND_NOW → full RELRO (GOT read-only after load)
✓ canary __stack_chk_fail referenced — stack protector linked in
· symbols not stripped — 2 symbol(s) in .symtabLive playground → (drop your own binary or click a sample — files never leave the browser)
import { parseElf } from './src/elf.js';
import { securityReport, dependencyGraph } from './src/analyze.js';
const elf = parseElf(bytes); // Uint8Array → full model
elf.findSection('.text'); // section or null
elf.needed; // ['libc.so.6', …]
securityReport(elf).checks; // [{name, status: good|warn|bad|info, detail}]
dependencyGraph(elf).edges; // [{from: '<main>', to: 'libc.so.6'}]src/reader.js is a bounds-checked reader: every field read validates its
range and throws an ElfError carrying the byte offset, so truncated
files fail loudly instead of producing garbage. src/elf.js walks ident →
header → section/program tables → symbol/relocation/dynamic tables and
resolves every string-table reference. src/analyze.js layers the
dependency graph, the security classification and the hex annotations on
top. Field-by-field documentation with offsets lives in
docs/FORMAT.md.
The samples are unusual: samples/tools/elf-build.mjs is a minimal ELF
writer that computes every header, table and string-table offset
explicitly, so each fixture is valid by construction and the tests can
assert exact values the writer produced.
| file | role |
|---|---|
src/reader.js |
bounds-checked u16/u32/u64 reader, ElfError |
src/elf.js |
full ELF32/64 model (sections, symbols, relocs, dynamic) |
src/analyze.js |
dependency graph + security report + hex annotations |
samples/tools/ |
byte-by-byte ELF builder + sample generator |
bin/elfscope.js |
CLI: info/sections/symbols/dynamic/security/graph/hex |
playground/ |
drop-zone UI with tabs + hex highlighting |
The project brief suggested Rust→WASM; it is deliberately pure JavaScript instead — same compiler-core decision as chip8-virtuoso: one zero-dependency parser that runs in Node (tests, CLI) and the browser (playground) with no toolchain and no build step.
16 tests: every sample parsed with exact expected values (the generator wrote them), 32/64-bit and MSB paths, truncation errors with offsets, security classifications per sample, dependency graph, hex annotations, and CLI integration (exit codes included).
- no disassembler, no DWARF, no .gdb_index — structure only, not semantics
- relocation names cover x86-64 + i386; other machines fall back to numeric
- huge (>2⁵³) addresses stay BigInt; the security logic only compares small values