Every article so far has assumed the file system still knows where a file begins and ends. This article drops that assumption entirely, and recovers files from nothing but a stream of raw bytes.
Analogy: The shredded letter
When a file is deleted, the operating system rarely erases its content; it only marks the space as free to reuse, the way tearing a letter from an address book leaves the page's content untouched until something else is written over it. File carving goes further than this: rather than trust a damaged or missing file system to say where a file begins and ends, it searches raw disk bytes directly for the signature every file format begins with, and cuts the file out wherever that signature appears.
File signatures
Every common format begins with a fixed sequence of bytes, often called its magic bytes, the same signature python-magic reads in the indexing article, now searched for directly across an entire disk image.
| Format | Signature (hex) | First bytes, as text |
|---|---|---|
| JPEG | FF D8 FF | (binary, not readable as text) |
| PNG | 89 50 4E 47 | .PNG |
25 50 44 46 | %PDF | |
| ZIP (and DOCX, XLSX) | 50 4B 03 04 | PK.. |
| GIF | 47 49 46 38 | GIF8 |
The algorithm
Python concept: Signature-based file carving
Input: a raw disk image and a table of known file signatures. Output: every recoverable file found, written out individually.
- Read the disk image as one continuous stream of bytes.
- Slide through the stream looking for a known starting signature.
- When a signature is found, note the starting position.
- Read forward until the format's known end marker, or a fixed maximum size.
- Write the bytes between start and end out as a recovered file.
- Continue searching from just after this file for the next signature.
Recovering JPEGs from raw bytes
simple_carver.py
# simple_carver.py: recover JPEG images from a raw image file
def carve_jpegs(disk_image_path, output_dir="carved"):
import os
os.makedirs(output_dir, exist_ok=True)
start_marker = b"\xff\xd8\xff"
end_marker = b"\xff\xd9"
with open(disk_image_path, "rb") as f:
data = f.read()
found = 0
pos = 0
while True:
start = data.find(start_marker, pos)
if start == -1:
break
end = data.find(end_marker, start)
if end == -1:
break
end += len(end_marker)
with open(f"{output_dir}/carved_{found}.jpg", "wb") as out:
out.write(data[start:end])
found += 1
pos = end
return found
if __name__ == "__main__":
print(f"Recovered {carve_jpegs('disk_image.dd')} images")
Run against a small test image built for this article, 1,065 bytes of unlabelled data with three JPEG-shaped byte sequences buried inside it, separated by stretches of ordinary-looking filler:
Sample output
Recovered 3 images
data.find(start_marker, pos) searches forward from wherever the last file ended, so the loop never rescans bytes it has already dealt with. pos = end after writing each file is what makes this a single pass over the whole image rather than one pass per file found.
What carving can and cannot promise
Why it carries weight: Why this matters
Carving has no idea whether a file system ever labelled these bytes as a file at all, which is exactly what makes it work even when the file system's own records are damaged, encrypted, or deliberately wiped. It also means a carved file can be incomplete or corrupted if the original was fragmented across the disk, a limitation every forensic report using this technique should state plainly.
Caution: A found signature is not a guarantee
The same three bytes that start a real JPEG can appear by coincidence inside completely unrelated data, and a carved file that reaches its end marker is not proof that every byte in between truly belonged to one original photograph. A carved result is always a candidate to be verified, for instance by checking whether the recovered bytes actually open as a valid image, never a confirmed finding on its own.
Check your understanding
Question 1: Why does deleting a file usually leave its content recoverable?
Deleting a file typically only marks the space it occupied as free to be reused; the file system stops pointing to it, but the underlying bytes are not erased until something else happens to be written over that same space. Until that overwrite happens, the original content is still there to be found.
Question 2: What does a file carver search for, and why does it not need a working file system to do its job?
It searches raw bytes directly for a format's known starting signature, such as FF D8 FF for a JPEG. Because it works on the bytes themselves rather than on the file system's records of where files begin and end, it can recover data even when those records are damaged, encrypted or deliberately wiped.
Question 3: Why can a carved file be incomplete or corrupted even when carving succeeds?
If the original file was fragmented, stored in non-contiguous pieces scattered across the disk, a carver that simply reads forward from a start signature to an end marker will capture whatever bytes happen to lie between them on the disk, not necessarily the correct, contiguous original content. The result can look plausible while actually being wrong or incomplete.
Key takeaway: Key takeaway
File carving searches raw bytes for a format's own signature and ignores the file system entirely, which is exactly why it still works when that file system is damaged or gone. It is a powerful last resort, and its results are always candidates to verify, never confirmed findings on their own.

