██████╗ ██╗███╗ ██╗ ██╗ █████╗
██╔══██╗██║████╗ ██║ ██║██╔══██╗
██████╔╝██║██╔██╗ ██║ ██║███████║
██╔══██╗██║██║╚██╗██║██ ██║██╔══██║
██████╔╝██║██║ ╚████║╚█████╔╝██║ ██║
╚═════╝ ╚═╝╚═╝ ╚═══╝ ╚════╝ ╚═╝ ╚═╝
ELF Binary Analysis & Patching Tool
A lightweight, no-frills ELF analysis tool built for reverse engineers, binary exploit developers, and CTF players. No bloat, no GUI, no libelf dependency — just raw ELF parsing, Capstone-powered disassembly, and a clean interactive REPL.
- Quick Start
- Features
- Installation & Build
- Usage
- Interactive Mode
- Command-Line Mode
- Command Reference
- Examples & Workflows
- Architecture & Internals
- Project Structure
- Limitations
- Troubleshooting
- Comparison to Other Tools
- Contributing
- Security Notice
- License
# Build
git clone https://github.com/akn-cybersec/binja
cd binja && sudo make
# Drop into interactive REPL
./binja ./target_binary
# One-shot command
./binja ./target_binary info
./binja ./target_binary disas main
./binja ./target_binary strings 6Common workflow for a CTF binary:
binja> info
binja> functions
binja> disas vuln_func
binja> xrefs 0x401234
binja> patch 0x401200 9090
binja> rop find --ret
| Feature | Description |
|---|---|
| 🔍 ELF Parsing | Manual header, section, segment, and symbol table parsing — zero libelf dependency |
| 📝 Disassembly | x86/x86-64 disassembly via Capstone engine |
| 🔗 Cross-Reference Finding | Scans .text for call/jmp/mov/j* instructions whose operand matches a target address |
| 💾 Binary Patching | In-memory and on-disk patching with automatic .bak backup creation |
| 📊 Protection Analysis | Detects NX, PIE, Stack Canary (symbol-based), and RELRO (none/full) |
| 📜 String Extraction | Pulls printable strings from .rodata and .data |
| 🎮 Interactive REPL | Persistent command history (with timestamps in the on-disk history file), saved to .binja_history |
| 🎮 ROP Gadgets Finder | Scans executable sections for gadgets ending in ret or syscall; supports filtering by type (--pop, --mov, --syscall), auto-chain building, and Python exploit export |
| 🔲 Hexdump | Hex + ASCII dump of any section, with optional offset and length |
| 📋 Section/Segment Listing | All ELF sections and program headers with flags and permissions |
- Linux x86-64
- GCC or Clang with C++17 support
- Capstone disassembly framework
- GNU Make
Arch Linux:
sudo pacman -S capstoneUbuntu / Debian:
sudo apt-get install libcapstone-devFrom source:
git clone https://github.com/capstone-engine/capstone
cd capstone && ./make.sh && sudo make install# Optimized release build (-O2)
make
# Debug build (-g -O0 -DDEBUG, then a clean rebuild)
make debug
# Clean build artifacts
make clean
# List available Makefile targets
make helpNote:
make debugcurrently just adds-g -O0 -DDEBUGon top of the release flags — there's no ASAN/UBSan instrumentation and no extra verbose logging wired up yet. If you want sanitizers, add-fsanitize=address,undefinedtoDEBUG_FLAGSin the Makefile yourself for now.
The resulting binary is ./binja. No install step required — run it from the project directory or copy it somewhere on your $PATH.
binja has two operating modes: interactive and command-line.
Launch with a binary path to enter the REPL:
./binja ./target_binary╔════════════════════════╗
║ BINJA ║
╚════════════════════════╝
Binary: ./target_binary
Type 'help' for available commands, 'exit' to quit
binja> _
The REPL appends every command to .binja_history in the current working directory, each entry timestamped, and reloads that file on the next launch so history expansion (!!, !5) still works across sessions. The history command itself just lists the in-session commands by index — it does not print the timestamps (those only live in the .binja_history file on disk).
History expansion:
| Shorthand | Action |
|---|---|
!! |
Repeat the last command |
!5 |
Repeat command #5 from history |
history |
Show in-session command history (no timestamps) |
Signal handling:
Ctrl+C does not exit — it prints Type 'exit' to quit and redraws the prompt. Use exit or quit (or Ctrl+D / an empty line) to leave.
Run a single command non-interactively — useful for scripting and piping output:
./binja ./binary info
./binja ./binary functions
./binja ./binary disas main
./binja ./binary strings 6
./binja ./binary xrefs 0x401234
hexdump,sections, andsegmentsare currently only wired up in interactive mode — see Limitations.
Display ELF metadata: entry point, architecture, endianness, a list of known section names, and detected binary protections.
binja> info
Entry point: 0x401080
Architecture: x86-64
Endianness: little
Sections: .text, .rodata, .data, .bss, .symtab, .strtab, .shstrtab, .comment, .note.gnu.build-id, .eh_frame...
Protections: NX: yes, PIE: no, Canary: no, RELRO: none
The section list comes from an internal hash map, so the order you see is arbitrary, not file order — and it's truncated to 10 entries with a trailing
...if there are more.
List all function symbols with their virtual addresses and sizes, sorted by address.
binja> functions
0x00401080 _start
0x004010b0 __libc_csu_init (size: 101)
0x00401156 main (size: 78)
0x004011a4 vuln (size: 63)
0x004011e3 win (size: 31)
If the binary has no .symtab/.dynsym FUNC entries it prints No functions found (binary may be stripped). Use disas 0x<address> to disassemble by address directly in that case.
Extract printable strings from .rodata and .data only. Default minimum length is 4. Output is just the raw strings, one per line — no addresses or section labels.
binja> strings 6
Enter your name:
Hello, %s!
cat /flag
/bin/sh
Disassemble a function by name (looked up in the symbol table) or by hex address. Internally this always locates the containing .text section, so functions living outside .text (e.g. in .init/.plt) won't disassemble correctly yet.
binja> disas vuln
0x004011a4: push rbp
0x004011a5: mov rbp, rsp
0x004011a8: sub rsp, 0x40
0x004011ac: lea rax, [rbp - 0x40]
0x004011b0: mov rsi, rax
0x004011b3: lea rdi, [rip + 0x84e]
0x004011ba: mov eax, 0
0x004011bf: call 0x401060
0x004011c4: nop
0x004011c5: leave
0x004011c6: ret
# By address
./binja ./binary disas 0x4011a4If the name isn't found, binja prints every known function name/address as a suggestion list.
Scan the disassembled .text section for call, jmp, mov, or any j*-mnemonic instruction whose operand string contains the target address in hex. Address must be given in hex (0x...).
binja> xrefs 0x4011e3
0x401200: call 0x4011e3
0x40123a: jmp 0x4011e3
If nothing matches, binja prints No cross-references found to 0x.... Note this is a textual match against the disassembled operand string, not a real data-flow/relocation analysis — it won't catch references built up across multiple instructions (e.g. lea+mov address computation) or references outside .text.
Patch the binary in memory and on disk at the given address with the provided hex byte string. A .bak backup of the original file is created automatically before the first write (interactive and CLI modes both hardcode backups on).
binja> patch 0x4011bf 9090
Patching at offset 0x11bf (2 bytes)
Backed up original to ./vuln.bak
Patched 2 bytes at 0x4011bf
# NOP out a call instruction (5 bytes)
binja> patch 0x4011bf 9090909090
# Overwrite a jump condition
binja> patch 0x401200 eb0e
⚠️ Patches are written to disk immediately. The.bakfile is your safety net — keep it until you are sure the patch is correct.
Dump section data in hex + ASCII format. Offset and length are optional (defaults: offset 0, length 256).
binja> hexdump .rodata 0 64
0x00000000: 45 6e 74 65 72 20 79 6f 75 72 20 6e 61 6d 65 3a |Enter your name:|
0x00000010: 20 00 48 65 6c 6c 6f 2c 20 25 73 21 0a 00 63 61 | .Hello, %s!..ca|
0x00000020: 74 20 2f 66 6c 61 67 00 2f 62 69 6e 2f 73 68 00 |t /flag./bin/sh.|
0x00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
List all ELF sections known to binja (from its internal name map) with address, size, offset, and flags.
binja> sections
Sections in binary:
.text addr=0x0000000000401080 size=0x1a3 offset=0x1080 [EXEC]
.rodata addr=0x0000000000402000 size=0x60 offset=0x2000
.data addr=0x0000000000404000 size=0x10 offset=0x3e00 [WRITE]
.bss addr=0x0000000000404010 size=0x8 offset=0x3e10 [WRITE]
.symtab addr=0x0000000000000000 size=0x240 offset=0x4000
.strtab addr=0x0000000000000000 size=0xe1 offset=0x4240
List program headers with offsets, virtual/physical addresses, sizes, and permission flags. Type names are printed without the PT_ prefix (LOAD, DYNAMIC, GNU_STACK, GNU_RELRO, INTERP, or UNKNOWN).
binja> segments
Program segments:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
LOAD 0x000000 0x00400000 0x00400000 0x0002e8 0x0002e8 R 0x1000
LOAD 0x001000 0x00401000 0x00401000 0x0001a3 0x0001a3 R E 0x1000
LOAD 0x002000 0x00402000 0x00402000 0x0000c4 0x0000c4 R 0x1000
LOAD 0x003e00 0x00403e00 0x00403e00 0x00022c 0x00023c RW 0x1000
GNU_STACK 0x000000 0x00000000 0x00000000 0x000000 0x000000 RW 0x10
Show the in-session command history by index (no timestamps — those are only recorded in the .binja_history file on disk).
binja> history
Command history:
1 info
2 functions
3 disas vuln
4 xrefs 0x4011e3
5 patch 0x4011bf 9090
Display command reference and usage hints in the REPL.
Exit interactive mode cleanly.
./binja ./challenge
binja> info # Entry point, protections
binja> sections # Identify interesting sections
binja> segments # Check NX (PT_GNU_STACK flags)
binja> functions # What's in the symbol table?
binja> strings 5 # Any juicy strings? /bin/sh? flag paths?
binja> rop find --ret # Any gadgets having retbinja> functions
# spot 'vuln' at 0x4011a4
binja> disas vuln
# see the scanf with a fixed buffer — classic overflow
binja> xrefs 0x4011a4
# find who calls vuln, trace execution pathbinja> disas check_auth
# 0x401320: test eax, eax
# 0x401322: jne 0x401380 <-- jump if auth fails
# Patch jne (75 XX) to jmp (eb XX) to always take the branch
binja> patch 0x401322 eb5c
binja> disas check_auth
# verify the patch landed correctlybinja> functions
# 0x4011e3 win (size: 31)
binja> disas win
# confirm it calls system("/bin/sh") or similar
binja> xrefs 0x4011e3
# verify it's never called legitimately — pure overflow target5. Extracting hidden strings from a crackme
binja> strings 8
# look for long strings — flags, passwords, keys
binja> hexdump .rodata
# when you need the raw bytes around a stringbinja> disas main
binja> disas vuln
binja> !! # re-runs: disas vuln
binja> !1 # re-runs: disas main
binja> history # review in-session historybinja is built around four cooperating modules:
┌─────────────────────────────────────────────────────┐
│ main.cpp │
│ CLI dispatch / Interactive REPL │
│ Command history / Signal handling │
└────────────┬──────────────────────┬────────────────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ elf_parser.cpp │ │ disassembler.cpp │
│ │ │ │
│ ELF64/32 hdr │ │ Capstone init │
│ Section table │ │ x86/x86-64 dis │
│ Program hdrs │ │ xref scanner │
│ .symtab │ └─────────────────┘
│ .dynsym │
│ .strtab │ ┌─────────────────┐
│ String extract │ │ patcher.cpp │
│ Protection det.│ │ │
└─────────────────┘ │ .bak creation │
│ In-memory patch│
│ On-disk write │
└─────────────────┘
ELF parsing is done entirely by hand — Elf64_Ehdr, Elf64_Shdr, Elf64_Phdr, Elf64_Sym structs (plus 32-bit equivalents, upconverted into the 64-bit structs) are read directly from the mmap'd file. No libelf, no BFD, no external parsing library.
Disassembly wraps the Capstone C API in a thin C++ class. cs_open, cs_disasm, and cs_close handle the lifecycle; the xref scanner disassembles the entire .text section up front and then string-matches each decoded instruction's operand text against the target address in hex.
Patching uses standard std::fstream seek/write (patch_file opens the target in ios::binary | ios::in | ios::out, seekps to the computed offset, and writes the new bytes) to overwrite bytes at the file offset corresponding to a given virtual address. That offset is computed by virtual_to_offset, which walks the PT_LOAD segments to find the matching p_offset + (vaddr - p_vaddr) translation — it does not use mmap for writing.
binja/
├── main.cpp # CLI entry point, REPL, command dispatch, history
├── elf_parser.cpp # Manual ELF parsing: headers, sections, symbols, strings, protections
├── disassembler.cpp # Capstone wrapper: disassembly engine, xref scanning
├── patcher.cpp # Binary patching with .bak backup creation
├── elf_parser.h # ElfParser class definition
├── disassembler.h # Disassembler class definition
├── patcher.h # Patcher class definition
├── Makefile # Build system: release / debug / clean / help targets
└── README.md
- ELF only — no PE (Windows) or Mach-O (macOS) support
- x86 / x86-64 — Capstone supports other archs but binja's section logic is currently wired for these two; other architectures will fail gracefully
- Limited 32-bit testing — 32-bit ELF parsing is implemented (structs are upconverted to 64-bit internally) but less battle-tested than 64-bit
- RELRO detection is binary —
check_protections()currently only reports"none"or"full"(it setsfullwhenever aPT_GNU_RELROsegment exists); there's no partial-RELRO distinction yet - Stack canary detection is symbol-based — it looks for a
__stack_chk_failsymbol in.symtab/.dynsym, so a canary-protected but fully stripped binary can show a false negative - ASLR/exec-stack fields aren't surfaced —
ProtectionInfohasaslr/execstackmembers but nothing currently populates or prints them xrefsis a textual match, not real data-flow analysis — it only catches references that appear as a literal hex operand on acall/jmp/mov/j*instruction within.textdisasis hardcoded to.text— functions outside.text(PLT stubs,.init, etc.) aren't handledhexdump,sections, andsegmentsare interactive-only — they aren't wired into command-line (one-shot) mode yet- Dynamic section parsing is a stub —
get_dynamic_entry()andget_plt_addresses()are unimplemented placeholders that always return empty/zero;get_imported_symbols()/get_exported_symbols()exist but aren't exposed through any CLI command yet - No DWARF parsing — debug info (file/line mapping) is not read; stripped binaries show addresses only
- No dynamic analysis — static analysis only; no tracing, no emulation
| Error | Cause | Fix |
|---|---|---|
Cannot resolve address 0x... |
Address falls outside all PT_LOAD segments | Verify the address with segments; make sure the binary is not PIE with ASLR active |
| Function not found, list of suggestions printed | Binary is stripped, no .symtab/.dynsym FUNC entries |
Use disas 0x<address> directly |
Capstone init failed |
Missing or incorrect Capstone install | Run ldd ./binja and check libcapstone.so resolves |
Permission denied when patching |
File is read-only | chmod u+w ./target before patching |
.binja_history not updating |
Directory not writable | Run binja from a directory you own |
binja is not a replacement for radare2, Ghidra, or Binary Ninja (the commercial product). It is a focused, lightweight CLI tool for the specific workflows that come up most in binary exploitation and CTF work.
| Capability | binja | readelf | objdump | radare2 |
|---|---|---|---|---|
| ELF section/segment listing | ✅ | ✅ | ✅ | ✅ |
| Disassembly | ✅ | ❌ | ✅ | ✅ |
| Symbol table listing | ✅ | ✅ | ✅ | ✅ |
| Cross-reference finder | ✅ (text-match) | ❌ | ❌ | ✅ |
| Binary patching (with backup) | ✅ | ❌ | ❌ | ✅ |
| Protection analysis | ✅ (NX/PIE/Canary/RELRO) | Partial | ❌ | ✅ |
| Interactive REPL w/ history | ✅ | ❌ | ❌ | ✅ |
| String extraction | ✅ | ❌ | ✅ | ✅ |
| Scripting / API | ❌ | ❌ | ❌ | ✅ |
| Dependency count | 1 (capstone) | 0 | 0 | Many |
Use binja when: you want fast answers in a CTF, you are writing exploit scripts and want quick one-liners, or you just don't want to wait for radare2 to load.
Use radare2/Ghidra/Binary Ninja when: you need decompilation, advanced scripting, graph views, partial-RELRO/ASLR-aware protection reports, or analysis of non-ELF formats.
1. Start with `info` — know your protections before anything else.
Remember RELRO here is only ever "none" or "full" right now.
2. `strings` before `disas` — a /bin/sh or a flag path tells you
immediately what the intended vector is.
3. Use `xrefs` to trace data flow backward — but remember it's a
text match on .text operands, so double-check anything subtle
(e.g. address built via lea + separate mov) by hand.
4. Keep patches surgical — NOP the minimum number of bytes,
verify with `disas` after every patch.
5. .bak files are sacred — never delete them until the patched
binary is confirmed working.
Bug reports, feature requests, and pull requests are welcome.
Before submitting a PR:
- Run
makeandmake debugcleanly - Test against at least one real ELF binary (statically linked and dynamically linked)
- Keep additions self-contained within the relevant source file
- Match the existing code style (C++17, no STL containers in the hot path if avoidable)
Planned / good-first-issue features:
- Partial-RELRO detection (currently collapsed to none/full)
- GOT/PLT table display (
get_plt_addresses/get_dynamic_entryare stubs today) - Wire
hexdump/sections/segmentsinto command-line (non-interactive) mode - 32-bit ELF test suite
- DWARF line info parsing (basic)
- JSON output mode for scripting integration
binja is a static analysis and patching tool intended for:
- Security research on software you own or have explicit written permission to analyze
- CTF challenges
- Reverse engineering for interoperability and vulnerability research under applicable law
Do not use binja to analyze or patch binaries without authorization. The authors are not responsible for misuse.
"Trust The Process" — My Princess
kaizen_dragon
MIT License
Copyright (c) 2025 kaizen_dragon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.