Autogenerated documentation with DeepWiki
Minimalistic and lightweight shell-based hex editor.
- Print file content in various formats.
- Write/overwrite data into the file.
- Undo writes until committed.
- Enumerate (ASCII) strings.
- Search strings or binary data.
- Calculate hashes, checksums, and CRCs.
- Execute template files using a custom language (bhe), see examples in the
templates/subdirectory. - Disassemble opcodes (using Capstone).
- Assemble opcodes (using Keystone).
- Identify the ISA of a byte block using bundled AI models, or graph code ranges across a file.
Just run bhex <file> to start the shell.
Supported flags:
Usage: bhex [ options ] inputfile
-h --help Print help
-w --write Open the file in write mode
-b --backup Backup original file in "filename.bk"
-2 --no_warning Disable warnings
-n --no_history Do not save command history
-C --no_color Do not use colors
-U --no_unicode Draw with ASCII only, never with unicode
-c "c1; c2; ..." Execute the commands given as argument and exit
-s --script Script mode (commands from raw stdin)
command history is saved in "$HOME/.bhex_history", it can be changed setting BHEX_HISTORY_FILE environment variable
colors are disabled automatically when the output is not a terminal, or when the NO_COLOR environment variable is set; unicode only when the locale of the environment is a UTF-8 one
The project can be compiled using cmake. Without Capstone and Keystone, it has no runtime dependencies (apart from libc), so it should be quite straightforward:
$ mkdir build
$ cd build
$ cmake ..
$ make
To enable the disassembler command, use "-DENABLE_CAPSTONE=on".
To enable the assembler command, use "-DENABLE_KEYSTONE=on".
To enable an ASAN build, use "-DASAN=on -DCMAKE_BUILD_TYPE=Debug".
To enable tests, use "-DENABLE_TESTS=on".
To enable fuzz test binaries, use "-DENABLE_FUZZ_TESTS=on".
To statically link libc, use "-DSTATIC_BUILD=on".
Every command has the following structure:
$ command_name/mod1/mod2/mod3/... arg1 arg2 ...
where the modifiers (e.g., mod1) are optional parameters of the command.
The documentation for each command can be accessed by typing "?" after the name of the command.
If you type "help" (or "h"), you get the list of commands:
[0x0000000] $ h
Available commands:
help [h]
setbase [sb]
echo [ec]
info [i]
isa_identify [ii]
interactive [tui]
entropy [e]
search [src]
hash [hh]
checksum [cs]
crc [cr]
findbase [fba]
strings [str]
template [t]
identify [id]
seek [s]
print [p]
diff [df]
export [ex]
import [im]
assemble [as]
disas [ds]
write [w]
delete [d]
undo [u]
commit [c]
Based on isadetect.
[0x0000000] $ ii?
isa_identify: identify the ISA of a block of bytes using bundled AI models
ii[/g] [<size>]
g: graph mode; scan the input in 1024-byte chunks, detect code ranges
size: number of bytes to analyze starting from the current offset
(if omitted, use the whole file)
Normal mode prints the top ISA candidates for the selected byte range.
Graph mode (ii/g) scans the selected region in 1024-byte chunks, classifies
which chunks likely contain executable code, merges contiguous chunks of the
same type, and prints only the detected code ranges together with the top ISA
for each range.
Example:
[0x0000000] $ ii/g 4096
ISA graph (4096 bytes analyzed, 1024-byte chunks):
[0x0000000000000000, 0x0000000000000800): x64, le (confidence: 96.42%)
[0x0000000000000c00, 0x0000000000001000): x86, le (confidence: 91.87%)
[0x0000000] $ sb?
setbase: set or display the base address
sb <base>
base: the new base address (if omitted, display current base)
[0x0000000] $ ec?
echo: print arguments to stdout
echo [/x|/d] <arg1> [arg2] ...
/x: force hexadecimal output for numbers (default)
/d: force decimal output for numbers
Expressions in backticks are evaluated before printing.
[0x0000000] $ i?
info: prints information about the opened binary
[0x0000000] $ e?
entropy: display an entropy graph
e [<rows> <len>]
rows: number of points in the graph (if omitted or '-', auto mode)
len: number of bytes to include starting from the current offset (if omitted, use the whole file)
[0x0000000] $ e - 8
[ 00000000 - 000277c8 ] (5.980) ---------------------------------+
[ 000277c8 - 0004ef90 ] (6.398) -----------------------------------+
[ 0004ef90 - 00076758 ] (6.492) ------------------------------------+
[ 00076758 - 0009df20 ] (4.491) -------------------------+
[ 0009df20 - 000c56e8 ] (6.441) ------------------------------------+
[ 000c56e8 - 000eceb0 ] (6.477) ------------------------------------+
[ 000eceb0 - 00114678 ] (6.495) ------------------------------------+
[ 00114678 - 0013be40 ] (4.388) ------------------------+
Start an interactive session.
[0x0000000] $ src?
search: search a string or a sequence of bytes in the file
src[/{x, s}/sk/p] <what>
x: data is a hex string
s: data is a string (default)
sk: seek to first match
c: print context
what: either a string or a hex string
[0x0000000] $ str?
enumerate the strings in the file (i.e., sequences of printable ascii characters with 8 or 16 bits)
str[/n/{a,w}] [ <pattern> <num> ]
n: look for null-terminated strings
a: 8-bit only
w: 16-bit only
pattern: print only strings that contain the pattern as substring (use * for any character)
num: minimum length (default: 3)
Based on binbloom.
[0x0000000] $ fba?
findbase: guess the base address of a raw firmware blob using binbloom heuristics
fba[/{32,64}/{le,be}]
32: assume a 32-bit blob (default)
64: assume a 64-bit blob
le: force little-endian decoding
be: force big-endian decoding
[0x0000000] $ hh?
hash: calculate the hash of <size> bytes at current offset + <off>
hash /l <algorithm> [ <size> <off> ]
l: list the supported hashing algorithms
algorithm: hashing algorithm (or '*' to use all supported algorithms)
size: number of bytes to include in the hash (if omitted or zero, hash the whole file starting from current offset)
off: starting offset wrt to current offset (default 0)
[0x0000000] $ hh/l
md2
md4
md5
md6-128
sm3
sha1
sha256
sha512
sha3-256
RipeMD-160
blake2s
blake2b
blake3
gost
groestl-256
jh-256
snefru-128
spectral-256
haval-128-5
haval-256-5
tiger
tiger2
whirlpool
...
[0x0000000] $ t?
template: parse the file at current offset using a 'bhe' template file
t[/l/i/x] <name or file>
l: list available templates and structs
x: output in XML
i: interpret inline code
arg: its meaning depends on the mode. It could be
- the name of the pre-loaded template/struct/proc to use
- a path to a template file
- a filter (if in list mode)
- inline bhex code (if in interpret mode)
[0x0000000] $ t/l
Available templates:
gpt
jpeg
gzip
mp4
ext
uimage
ubifs
rpm
mp3
7z
pcapng
dtb
pdf
elf
png
mbr
lzo
pcap
sfnt
riff
tar
zip
sqlite3
zstd
cpio
pe
gif
ogg
squashfs
bzip2
ar
javaclass
wasm
dex
x509
fat
xz
macho
...
Walks the file looking for the formats bhex knows, the way binwalk does. Each template declares
the byte patterns its format cannot appear without (_identify_magic), and the bytes it needs to
see to be sure (_identify). One pass finds every declared pattern at once, and only where one
matched does the template get asked; a template that recognises the bytes answers with the size of
what it found, and the scan resumes past it.
[0x0000000] $ id?
identify: scan the file for known formats, running the '_identify' proc of
every template that declares one at every offset
id[/l/v/n/e] [<len>]
l: list the templates that take part in the scan
v: report the time each template cost (measuring it is not free,
the scan itself gets slower)
n: do not skip over what was identified
e: exhaustive: ignore the declared magics and ask every template
at every offset. Comparing 'id/n' with 'id/n/e' is how a wrong
magic declaration gets caught
len: number of bytes to scan starting from the current offset
(if omitted, scan up to the end of the file)
A hit reports the size the template gave for what it recognised,
and the scan resumes past it -- so a format embedded in something
already identified is only found with '/n'
[0x0000000] $ id
0x00100000 png 218 bytes
0x002000da elf 324 bytes
0x0030021e zip 71 bytes
0x0040058b gzip 10 bytes
0x005005aa jpeg 20 bytes
0x00600747 mp4 262 bytes
0x0070084d squashfs 623 bytes
0x0080184d rpm 96 bytes
12 hits in 8395032 bytes, 38 templates
prefilter: 72 patterns -> 157 candidates in 0.030s
157 offsets, 157 runs in 0.034s
A template with no _identify_magic has to be tried at every offset, which puts a floor under the
whole scan; id/l shows which templates are prefiltered and id/v what each one costs.
[0x0000000] $ s?
seek: change current offset
s[/{+,-}] <addr>
+: sum 'off' to current offset (wrap if greater than filesize)
-: subtract 'off' from current offset (wrap if lower than zero)
off: can be either a number or the character '-'.
In the latter case seek to the offset before the last seek.
If a base address is set (see 'sb'), absolute addresses are
relative to the base and cannot go below it.
NOTE: if called without arguments, print current offset
[0x0000000] $ crc?
import: calculate the CRC <name> at current offset + <off>
crc[/l] <name> [<size> <off>]
l: list the supported crc names
name: name of the CRC (or a partial name, or '*')
size: number of bytes to include in the crc (if omitted or zero, import the whole file starting from current offset)
offset: starting offset of the imported file (if omitted, import from current offset)
[0x0000000] $ cs?
checksum: calculate a checksum at current offset + <off>
checksum [/l] <name> [<size> <off>]
l: list the supported checksum names
name: name of the checksum (or a partial name, or '*')
size: number of bytes to include (if omitted or zero, use the whole file starting from current offset)
offset: starting offset (if omitted, use current offset)
[0x0000000] $ as?
assemble: assemble code and write it at current offset
as[/l/i/s] <arch> 'instr1; instr2; ...'
l: list supported architectures
i: insert instead of overwrite
s: seek to the end of the write
arch: the architecture to use
code: assembly code string (e.g., "inc eax; inc ecx; ret")
[0x0000000] $ ds?
disas: disassemble code at current offset
ds[/l|/a] <arch> [<nbytes>]
l: list supported architectures
a: draw the branches as arrows on the left of the mnemonics.
'◂' marks a jump, '▸' where it lands, '▾' and '▴' a
target that is not part of the listing
arch: the architecture to use
nbytes: number of opcodes to disassemble (default: 8)
With /a, every branch whose target is disassembled too is drawn as a line going from the jump
to the instruction it lands on, so that the loops and the early exits of a function can be seen
without following the addresses by hand:
[0x0215800] $ ds/a m68k 40
[...]
0x00215832: 6e 08 ╭◂ bgt.b $21583c
0x00215834: 20 3c ff ff fb b5 │ move.l #$fffffbb5, d0
0x0021583a: 60 36 ╭───◂ bra.b $215872
0x0021583c: 4a 88 │ ╰▸ dc.w $4a88
0x0021583e: 67 04 │ ╭◂ beq.b $215844
0x00215840: 4a 8c │ │ dc.w $4a8c
0x00215842: 66 08 │ ╭─◂ bne.b $21584c
0x00215844: 20 3c ff ff fb b6 │ │╰▸ move.l #$fffffbb6, d0
0x0021584a: 60 26 │╭──◂ bra.b $215872
0x0021584c: 4a ad 00 34 ││╰─▸ tst.l $34(a5)
[...]
0x00215870: 70 00 │││╰▸ moveq #$0, d0
0x00215872: 4c ee 30 80 ff f4 ╰┴┴─▸ movem.l -$c(a6), d7/a4-a5
Nested branches take one column each, up to five of them. Past that, and for the branches
whose target is not part of the listing, only the direction is marked with ▾ or ▴. Where
several branches land on the same instruction the lines join, as they do on the last row above.
The arrows need the branch targets that capstone reports for the architecture: bpf and ebpf get
none, as capstone does not tell their jumps apart from the rest.
The drawings need a UTF-8 locale, which is looked up in LC_ALL, LC_CTYPE and LANG. Any
other locale, a TERM of dumb, or -U on the command line, and the same arrows are drawn
with ASCII instead:
0x0021583a: 60 36 /---< bra.b $215872
0x0021583c: 4a 88 | \> dc.w $4a88
0x00215872: 4c ee 30 80 ff f4 \++-> movem.l -$c(a6), d7/a4-a5
[0x0000000] $ p?
print: display the data at current offset in various formats
p[/{x,w,d,q,a,C}/{le,be}/r/W/{+,-}] <nelements>
x: hex output (default)
w: words
d: dwords
q: qwords
a: as ascii
C: as C buffer
le: little-endian (default)
be: big-endian
r: raw mode (no ascii, no header and no addresses)
W: wide mode (print 32 bytes per line)
+: seek forward after printing
-: seek backwards after printing
nelements: the number of elements to display
(default: enough to display 256 bytes, if '-' the whole file)
[0x0000000] $ df?
diff: prints the differences with another file
df[/p/w/n] <file>
p: print different bytes
w: wide print (rows are 16 bytes)
n: do not use colors
file: path to the file to compare
[0x0000000] $ ex?
export: write <size> bytes of the file starting from current offset to <ofile>
ex <ofile> [<size>]
ofile: output file
size: number of bytes to export (if omitted, all the remaining bytes)
[0x0000000] $ im?
import: import the content of <file> at current offset
im[/{ovw,i}] <file> [<size> <offset>]
i: insert in current file (default)
ovw: overwrite current file
file: input file
size: number of bytes to import (if omitted or zero, import the whole file)
offset: starting offset of the imported file (if omitted, import from offset 0)
[0x0000000] $ w?
write: write data at current offset
w[/{s,x,b,w,d,q}/{le,be}/u/i] <data>
s: string input (default)
x: hex input
b: byte
w: word
d: dword
q: qword
le: little-endian (default)
be: big-endian
u: unsigned
i: insert
data: the data to write. The format depends on the type of
write. Here are some examples:
w/x "00 01 02 03"
w/s "a string"
w/q/be 0x1234
[0x0000000] $ d?
delete: delete bytes at current offset (all remaining bytes if the argument is omitted)
d [<nbytes>]
[0x0000000] $ u?
undo: undo the last write
u[/a]
a: undo all
[0x0000000] $ c?
commit: commit all writes to file
c[/l]
l: list uncommitted changes