____ _ _
| _ \| | | |
| |_) | |__| | _____ __
| _ <| __ |/ _ \ \/ /
| |_) | | | | __/> <
|____/|_| |_|\___/_/\_\
Minimalistic and lightweight shell-based hex editor.
It is designed to have a low memory footprint. This makes the program usable on very low-end devices.
Supported features:
- print file content in various format
- write/overwrite data into the file
- undo writes until committed
- enumerate (ascii) strings
- search strings or binary data
- execute template files using a custom language (look in the templates subdirectory to see some example)
- disassemble opcodes (using Capstone)
- assemble opcodes (using Keystone)
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 "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
The project can be compiled using cmake. Without Captone and Keystone, it has no runtime dependencies (apart from libc), so it should be quite strightforward:
$ 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".
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 of a each command can be accessed 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]
interactive [int]
info [i]
entropy [e]
search [src]
strings [str]
template [t]
seek [s]
print [p]
diff [df]
export [ex]
import [im]
assemble [as]
disas [ds]
write [w]
delete [d]
undo [u]
commit [c]
[0x0000000] $ i?
info: prints information about the opened binary
[0x0000000] $ e?
entropy: display an entropy graph
e <len> <rows>
len: number of bytes to include starting from the current offset (if omitted or '-', the whole file)
rows: number of points in the graph (if omitted, defaults to 32)
[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] <data>
x: data is an hex string
s: data is a string (default)
sk: seek to first match
p: print blocks info
data: either a string or an hex string
[0x0000000] $ str?
enumerate the strings in the file (i.e., sequences of printable ascii characters)
str[/n] [<num>]
n: look for null-terminated strings
num: minimum length (default: 3)
[0x0000000] $ t?
template: parse the file at current offset using a 'bhe' template file
t[/l/ls] <name or file>
l: list available templates
ls: list available structs
name: the name of the pre-loaded template/struct to use, of a path to a template file
[0x0000000] $ t/l/ls
Available templates:
elf
tar
Available template structs:
elf.Elf64_Shdr
elf.Elf64_Phdr
elf.Elf32_Phdr
elf.Elf_Ehdr
elf.ElfIdent
elf.Elf32_Shdr
tar.TarHeader
[0x0000000] $ s?
seek: change current offset
s[/{+,-}] <off>
+: sum 'off' to current offset (wrap if greater than filesize)
-: subtract 'off' to 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.
NOTE: if called without arguments, print current offset
[0x0000000] $ as?
assemble: assemble code and write it at current offset
as[/l/i/s] <arch> "<code>"
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] <arch> [<nbytes>]
l: list supported architectures
arch: the architecture to use
nbytes: the number of opcodes to disassemble, default value: 8
[0x0000000] $ p?
print: display the data at current offset in various formats
p[/{x,w,d,q}/{le,be}/r/{+,-}] <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)
+: seek forward after printing
-: seek backwards after printing
nelements: the number of elements to display
(default: enough to display 256 bytes)
[0x0000000] $ df?
diff: prints the differences with another file
df[/p] <file>
p: print different bytes
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
[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 there 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
d <len>
[0x0000000] $ u?
undo the last write
[0x0000000] $ c?
commit all the writes to file