Digital Forensics / Basic: scope, integrity, and interpretation / Lesson 4
Read bytes, encodings, and offsets precisely
Interpret small synthetic byte sequences without confusing display text, byte positions, and numeric layout.
On this page
Lesson goals
- Distinguish bytes from decoded characters and preserve decoding failures.
- Interpret declared byte order and bounds in a tiny custom format.
Treat a display as one interpretation of bytes
An artifact begins as bytes, while a tool may display text, hexadecimal, integers, or a decoded structure. These views can answer different questions. Hexadecimal is a representation of bytes, not an encoding that automatically reveals meaning. Record the offset convention, length, and selected view. A byte offset is not necessarily a character index in UTF-8. In the workshop fixture, the Arabic title contains characters that occupy more than one byte. Treat the original byte stream and any decoded text as related but distinct analytical objects.
Declare the codec and error policy
Decode with a supported, explicitly chosen codec and preserve what happens when it fails. Ignoring invalid bytes can remove meaningful material; replacement characters can obscure which original bytes were present. The first program round-trips a known UTF-8 fixture and separately catches a deliberate invalid byte. The invalid case is handled by the complete program rather than leaving a crashing fragment. It demonstrates decoder behavior only. A real artifact’s encoding must be supported by its format or context instead of guessed from one readable word or silently forced to UTF-8.
text = "ورشة"
raw = text.encode("utf-8")
assert raw.decode("utf-8") == text
assert len(raw) > len(text)
try:
b"\xff".decode("utf-8", errors="strict")
except UnicodeDecodeError:
invalid_rejected = True
else:
invalid_rejected = False
assert invalid_rejected
print("UTF-8 round trip and invalid-byte rejection checked")
UTF-8 round trip and invalid-byte rejection checked
The complete program uses known public text and one inert invalid byte in memory. It checks UTF-8 behavior without reading a file or repairing any artifact. Arabic text is preserved exactly in both language versions of the code.
Make byte order and field length explicit
A multi-byte integer needs a specified width and byte order. Python struct can decode a declared format, while its native default may depend on platform layout. The second program uses explicit little-endian and big-endian unsigned sixteen-bit interpretations of the same two bytes. Both calculations are valid under their own assumptions; only the artifact specification can choose the relevant one. Do not pick whichever value fits the favored timeline. Bounds checks must precede extraction so truncated material is identified rather than padded with invented bytes or read at an unintended offset.
import struct
raw = bytes.fromhex("01 02")
assert len(raw) == 2
little = struct.unpack("<H", raw)[0]
big = struct.unpack(">H", raw)[0]
assert little == 513
assert big == 258
print("little=513 big=258 for the same two bytes")
little=513 big=258 for the same two bytes
The complete program decodes a two-byte synthetic value with explicit layouts. It is not a parser for a filesystem, packet capture, memory image, or real evidence format. No device, file, or network is accessed.
Preserve extraction boundaries and original values
When extracting a field, record start offset, byte length, original hex, and the decoding rule. Distinguish absolute offsets in the source fixture from offsets relative to a selected record. A parser that skips a header changes the reference point, so the report should state that convention. Keep original values alongside normalized fields, especially when malformed input cannot be decoded under the chosen rule. Do not relabel an offset as a sector, block, or character position without the relevant format definition. Unit confusion can create a convincing but incorrect artifact association.
Validate interpretations against known fixtures
A useful parser fixture includes a known valid value, a boundary length, a truncated case, and an unsupported encoding where relevant. Compare expected offsets and values independently from the implementation under review. A program that runs without an exception can still interpret the wrong byte order or silently discard bytes. Preserve failure categories and the exact input for replay. This course’s small calculations teach interpretation discipline; they do not validate a commercial forensic tool or establish that an unknown real-world format has been decoded correctly.
Common errors and fixes
Character index is reported as byte offset
- Why it happens
- Decoded text positions are confused with source bytes.
- How to fix it
- Track byte boundaries before decoding and label units.
- Prevent it next time
- Include multibyte fixtures in parser validation.
Invalid bytes are ignored silently
- Why it happens
- The decoder removes material without a recorded decision.
- How to fix it
- Preserve original bytes and report the decoding failure.
- Prevent it next time
- Make codec and error policy explicit.
Native layout is assumed portable
- Why it happens
- Byte order and alignment are left to runtime defaults.
- How to fix it
- Use the artifact’s declared field layout and bounds.
- Prevent it next time
- Test known byte sequences with independent expected values.
Check your understanding
1. The same bytes decode as 258 and 513. Which result belongs in the report?
Show worked solution
State the original bytes and both assumptions until the format definition establishes the correct byte order. If the format specifies big-endian unsigned sixteen-bit, 258 is the relevant interpretation. Do not select the value merely because it matches a preferred explanation.
2. Why preserve invalid UTF-8 bytes instead of replacing the source with readable text?
Show worked solution
Replacement changes the analytical object and can hide the original sequence. Keep the source intact, record strict-decoding failure, and create a clearly labeled derivative if a display policy is needed. Report what the derivative can and cannot establish about the original.
Key takeaways
- Bytes, characters, and display formats are different representations.
- Explicit layouts and bounds prevent ambiguous numeric extraction.
- Preserve decoding failures and original values.