Why a Forensic Disk Image Still Holds Files the Filesystem Deleted
Run rm on a file and the filesystem stops listing it. The bytes are still on the disk. A raw image copies the disk, not the filesystem's opinion of it, so the "deleted" content comes along for the ride.
Here is a thirty-second demonstration, so you do not have to take my word for it.
Delete a file and then find it anyway
This uses a 64 MiB file as a fake disk. It needs root for the mount, and it touches nothing real.
truncate -s 64M disk.img
mkfs.ext4 -q disk.img
mkdir mnt
sudo mount -o loop disk.img mnt
echo "needle-7f3a-was-here" | sudo tee mnt/secret.txt
sync
sudo rm mnt/secret.txt
sync
sudo umount mnt
grep -a -b -o 'needle-7f3a' disk.img
The last command prints a byte offset. The text is still in the image, sitting in a block the filesystem now considers free. Nothing was wiped; nothing needed to be.
What delete actually changes
Deleting is bookkeeping. Zeroing the data would mean writing every block of a file, which is slow and pointless when the space is about to be marked reusable anyway. So filesystems do the cheap part:
- Remove or hide the directory entry that maps the name to an inode.
- Mark the inode as free, and record a deletion time.
- Clear the blocks' bits in the allocation bitmap.
The data blocks are not on that list. They stay as they were until something else is allocated there and overwrites them, which might be seconds or years.
Quick detour: the name often survives too
On ext4 a directory is a list of variable-length entries. Unlinking a name does not shift the rest of the list; it extends the previous entry's length so it swallows the dead one. The dead entry's bytes are still sitting inside the gap.
You can see them with debugfs:
debugfs -R 'ls -d /' disk.img
Deleted entries show up wrapped in angle brackets. Whether you get the file's contents back from that is another matter, because ext4 tends to clear the extent information in the inode on delete. The name survives more reliably than the map to the data. Right, back to the main thread.
Other filesystems, same trick, different leftovers
The details differ, but the shape is the same everywhere:
- FAT: the first byte of the directory entry is replaced with
0xE5. The start cluster and size remain in the entry. The FAT chain is zeroed, so a fragmented file is harder to rebuild than a contiguous one. - NTFS: the MFT record's in-use flag is cleared. The record, including its filename and data runs, stays until the slot is reused for a new file.
- ext4: inode marked free with a deletion time; blocks freed in the bitmap; data untouched.
An examiner working from the image can read these structures directly, without asking the mounted filesystem anything. That is why tools that carve unallocated space can list names and timestamps for files nobody can see in a file manager.
Places copies hide that are not "deleted" at all
Even a file that was overwritten properly can leave traces elsewhere in the image:
- Slack space. A 100-byte file occupies a whole block. Whatever was in the rest of that block from a previous file stays there.
- The journal. ext4 and NTFS log metadata changes (ext4 can log data too, depending on mount mode). Old copies of inodes or directory blocks can sit in the journal after the live ones changed.
- Swap and hibernation files. Memory pages, including plaintext that was never meant to touch disk, get written there.
- Temp files and caches. Editors, thumbnailers and browsers make their own copies, which the user never deleted because the user never knew about them.
Why the image sees it but the mount does not
A mounted filesystem answers questions through its own metadata: "list this directory", "read this inode". Free space is, by definition, not reachable that way. An image is just bytes at offsets, and grep -a does not care what the allocation bitmap says.
That also explains why imaging a block device is different from copying files. A file-level copy only moves what the filesystem will admit to. A bit-for-bit copy moves everything, including the free blocks and the slack.
When the data really is gone
Three things can make the demo fail on real hardware:
- Overwriting. New files reuse freed blocks. A busy disk loses deleted content fast.
- Discard or TRIM. On SSDs, a mount with
discardor a periodicfstrimtells the drive the blocks are unused, and many drives then return zeroes for them. - Encryption and secure erase. With full-disk encryption, the image holds ciphertext; destroying the key makes every block, deleted or not, unreadable.
My loop-file demo dodges all three: it is a plain file on a host filesystem, nothing discarded, nothing reused. A real SSD is the case where you should not expect the needle to turn up.
Practical upshot
If you need a file gone from a disk you are giving away, rm is not the tool, and overwriting a single file is unreliable on journalling or flash-based storage. Encrypt from day one and destroy the key, or wipe the whole device. If you are the one examining an image, work on a copy, verify its hash first, and remember that "not in the directory listing" tells you very little about what is on the platter.