Skip to content

akn-cybersec/binja

Repository files navigation

binja — Binary Analysis Tool for ELF Files

██████╗ ██╗███╗   ██╗     ██╗ █████╗
██╔══██╗██║████╗  ██║     ██║██╔══██╗
██████╔╝██║██╔██╗ ██║     ██║███████║
██╔══██╗██║██║╚██╗██║██   ██║██╔══██║
██████╔╝██║██║ ╚████║╚█████╔╝██║  ██║
╚═════╝ ╚═╝╚═╝  ╚═══╝ ╚════╝ ╚═╝  ╚═╝
   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.


Table of Contents


Quick Start

# 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 6

Common workflow for a CTF binary:

binja> info
binja> functions
binja> disas vuln_func
binja> xrefs 0x401234
binja> patch 0x401200 9090
binja> rop find --ret

Features

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

Installation & Build

Dependencies

  • Linux x86-64
  • GCC or Clang with C++17 support
  • Capstone disassembly framework
  • GNU Make

Install Capstone

Arch Linux:

sudo pacman -S capstone

Ubuntu / Debian:

sudo apt-get install libcapstone-dev

From source:

git clone https://github.com/capstone-engine/capstone
cd capstone && ./make.sh && sudo make install

Build binja

# 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 help

Note: make debug currently just adds -g -O0 -DDEBUG on 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,undefined to DEBUG_FLAGS in 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.


Usage

binja has two operating modes: interactive and command-line.

Interactive Mode

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.


Command-Line Mode

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, and segments are currently only wired up in interactive mode — see Limitations.


Command Reference

info

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.


functions

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.


strings [min_len]

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

disas <function_name|address>

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 0x4011a4

If the name isn't found, binja prints every known function name/address as a suggestion list.


xrefs <address>

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 <address> <hex_bytes>

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 .bak file is your safety net — keep it until you are sure the patch is correct.


hexdump <section> [offset] [length]interactive mode only

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   |................|

sectionsinteractive mode only

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

segmentsinteractive mode only

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

historyinteractive mode only

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

helpinteractive mode only

Display command reference and usage hints in the REPL.

exit / quit

Exit interactive mode cleanly.


Examples & Workflows

1. Full binary recon

./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 ret

2. Identifying a vulnerable function and its callers

binja> 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 path

3. Patching out a security check

binja> 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 correctly

4. Locating a win function for a ret2win

binja> 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 target

5. 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 string

6. Using history expansion

binja> disas main
binja> disas vuln
binja> !!         # re-runs: disas vuln
binja> !1         # re-runs: disas main
binja> history    # review in-session history

Architecture & Internals

binja 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.


Project Structure

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

Limitations

  • 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 binarycheck_protections() currently only reports "none" or "full" (it sets full whenever a PT_GNU_RELRO segment exists); there's no partial-RELRO distinction yet
  • Stack canary detection is symbol-based — it looks for a __stack_chk_fail symbol in .symtab/.dynsym, so a canary-protected but fully stripped binary can show a false negative
  • ASLR/exec-stack fields aren't surfacedProtectionInfo has aslr/execstack members but nothing currently populates or prints them
  • xrefs is a textual match, not real data-flow analysis — it only catches references that appear as a literal hex operand on a call/jmp/mov/j* instruction within .text
  • disas is hardcoded to .text — functions outside .text (PLT stubs, .init, etc.) aren't handled
  • hexdump, sections, and segments are interactive-only — they aren't wired into command-line (one-shot) mode yet
  • Dynamic section parsing is a stubget_dynamic_entry() and get_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

Troubleshooting

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

Comparison to Other Tools

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.


Tips for Reverse Engineering Workflows

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.

Contributing

Bug reports, feature requests, and pull requests are welcome.

Before submitting a PR:

  • Run make and make debug cleanly
  • 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_entry are stubs today)
  • Wire hexdump/sections/segments into command-line (non-interactive) mode
  • 32-bit ELF test suite
  • DWARF line info parsing (basic)
  • JSON output mode for scripting integration

Security Notice

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.


Author

"Trust The Process" — My Princess

kaizen_dragon


License

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.

About

binja - Lightweight ELF binary analysis tool with interactive REPL, Capstone disassembly, cross-reference finding, and binary patching. Perfect for reverse engineering and CTF challenges.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages