Raw binary¶
Level: 101 → 201 · for anyone who has ever imported a firmware dump
One line: Raw Binary is the loader that accepts every file, at the lowest priority, and asks the reader for everything the other formats carry in their headers — the processor, the byte order, the width, and a base address — because a pointer in the file is only a pointer under one base and a number under every other.
What the loader reads¶
Nothing. A raw binary is bytes with no header: a ROM dump, a bootloader, a firmware image cut out of a flash chip, a section somebody extracted from a real executable. Every question this chapter's other formats answer in a header goes to a dialog instead:
| option | what it stands in for |
|---|---|
| the language, from a list | e_machine, cputype, Machine — and with it the byte order and the width |
| Base Address | e_entry, ImageBase, a mapping table: where the bytes go |
| File Offset and Length | which bytes: a slice of the file, defaulting to all of it |
| Block Name | a section name |
| Overlay | whether the block shadows another |
Ghidra's BinaryLoader is tiered as an UNTARGETED_LOADER, which means it is offered for every file, after any loader that recognised something, and it never has an opinion about the language.
In Python¶
Verified output of raw_binary_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. FOUR BYTES, AND WHAT EACH READING MAKES OF THEM
------------------------------------------------------------------------
u32 little '<I' 1065353216
u32 big '>I' 32831
i32 little '<i' 1065353216
f32 little '<f' 1.0
f32 big '>f' 4.600602988224807e-41
two u16 little '<HH' (0, 16256)
two u16 big '>HH' (0, 32831)
four bytes '4B' (0, 0, 128, 63)
00 00 80 3f is 1.0 as a little-endian float and 32,831 as a big-
endian one; 1,065,353,216 or 32,831 as an integer. Every other
loader here has a header that picks one row. Raw Binary has a
dropdown, and the reader picks.
2. A POINTER IS A POINTER UNDER ONE BASE
------------------------------------------------------------------------
34 bytes: a table of four 32-bit little-endian values, then four strings
10 00 00 08 16 00 00 08 1c 00 00 08 1f 00 00 08
63 61 66 c3 a9 00 68 65 6c 6c 6f 00 c5 bc 00 6f 6b 00
base 0x08000000 'café', 'hello', 'ż', 'ok'
base 0x00000000 out of range, out of range, out of range, out of range
base 0x08000100 out of range, out of range, out of range, out of range
base 0x07fffffc '�', 'o', 'k', out of range
The same 16 bytes of table read four ways. Under the base the file
was built for, every value lands on a string. Under base 0 they
point 128 MB past the end; four bytes lower, each lands four bytes
into its string. The file does not say which base is right, and
the loader has no way to tell a pointer from a number.
3. THE DIALOG'S FIELDS ARE A SLICE AND A PLACEMENT
------------------------------------------------------------------------
File Offset 0 Length 34 Base Address 0x08000000 -> 34 bytes at 0x8000000..0x8000021 10 00 00 08 16 00 00 08 ...
File Offset 16 Length 20 Base Address 0x08000010 -> 18 bytes at 0x8000010..0x8000021 63 61 66 c3 a9 00 68 65 ...
File Offset 16 Length 5 Base Address 0x00001000 -> 5 bytes at 0x1000..0x1004 63 61 66 c3 a9
File Offset and Length cut the file; Base Address says where the
cut goes; Block Name and Overlay name the result. Nothing is
parsed, so nothing can be wrong, and nothing can be checked.
4. WHAT EVERY OTHER FORMAT HERE WOULD HAVE SAID
------------------------------------------------------------------------
question a header answers with Raw Binary answers with
which end ELF byte 5, a Mach-O magic, PE by decree the language's, chosen from a list
how wide ELF byte 4, the PE optional magic the language's
where it loads e_entry, ImageBase, a mapping table Base Address, typed in
what it is a magic number nothing: every file qualifies
Ghidra's BinaryLoader is an UNTARGETED_LOADER: it appears in the
list for any file, last, with no opinion. It is the only loader
whose acceptance test is empty, and the only one that cannot be
wrong about a file, because it claims nothing about it.
The same four bytes, eight readings¶
Section 1 is chapter 1 restated for a loader. 00 00 80 3f is 1.0 as a little-endian float, 32,831 as a big-endian integer, 1,065,353,216 as a little-endian one, and four bytes — and nothing in the file prefers a row. Every other loader here reads a header that picks one: ELF from two bytes, Mach-O from how its magic reads, PE from a document. Raw Binary has a dropdown labelled Language, and the language's name — x86:LE:64:default, ARM:BE:32:v8 — carries the byte order and the width the file does not.
A pointer is a pointer under one base¶
Section 2 is what the base address is for. A table of four 32-bit values followed by four strings is a table of pointers if, and only if, the loader puts the file at the address the values assume; under any other base the same values point outside the file, or four bytes into each string, and there is no error, because to a loader a pointer and a number are the same thirty-two bits. That is the fact the other formats' headers exist to prevent: ImageBase, e_entry, a dyld mapping — each is a place in the file that says I was built for this address. A raw file says nothing, and choosing the base is the first and largest guess an analyst makes about one. Guess wrong and every cross-reference in the disassembly is wrong, quietly, which is why the option is not a detail.
What Ghidra checks¶
BinaryLoader ↗ returns a load spec for any provider, with no preferred language; its validateOptions checks that the base address parses in the chosen language's address space, that File Offset is inside the file and Length fits after it, and nothing else. Its name is Raw Binary. It is the only loader on the list whose acceptance test is empty, and so the only one that can never be wrong about a file, because it claims nothing about it — and the loader every hex-format file becomes once its records are placed, which is why Intel Hex and Motorola Hex offer the same list of languages.
If you are coming from Python or ABAP¶
Python. struct.unpack with a format string is the dialog: '<I' is a language choice and an offset into data is the file offset. There is no base address in Python because there is no memory image — the program's read_string(blob, base, pointer) supplies one by subtraction, and that subtraction is the whole of what a raw loader does with a pointer. Keep the base in one variable and pass it everywhere, because a hard-coded - 0x08000000 is a guess written down where it cannot be changed.
ABAP. (Not machine-checked — CI cannot run ABAP.) An xstring with no accompanying structure is a raw binary, and ASSIGN ... CASTING lays a type over it exactly as the language dropdown does — with the platform's byte order, which is the reading nobody chose. A field that holds an offset into the same xstring is a pointer under one base, the start of the string, and becomes a number the moment the string is copied into a longer one with a header in front. Store offsets relative to something the data carries, or store the base beside them.
Try it¶
head -c 64 /bin/ls > raw.binand import it into Ghidra as Raw Binary at base 0, then at0x100000000. Compare the disassembly of the same bytes.- Take a firmware image for any microcontroller and find its vector table: the first few 32-bit words are pointers, and only one base makes them land inside the file.
python3 -c "import struct; print(struct.unpack('<f', bytes.fromhex('0000803f')))"and then with'>I','<i','<HH'.- Import a
.hexfile and note the language list Ghidra offers. Then import the same bytes as Raw Binary and compare. - Pick a file you know and set File Offset and Length to cut its header off. The loader will not object; it cannot.
See also¶
- The bytes do not say which end — the question the dropdown answers
- Arithmetic has its own width — the width the dropdown answers
- Hex: a number, or a picture of bytes — a pointer and a number, the same bytes
- Intel Hex — a raw image with addresses attached, which is why its loader offers the same list
- ELF — what a header that answers all four questions looks like