Artifact & Deleted-File Recovery with The Sleuth Kit
- Explain why a deleted file's data blocks survive on disk until they're overwritten, even after the name is gone
- Walk a forensic image layer by layer with The Sleuth Kit: mmls for partitions, fls for files, istat for inode metadata, icat for content
- Recover a deleted file end-to-end — locate it in fls, read its inode, stream it out with icat, and verify the result with a hash
- Build a body file with fls -m and understand how it feeds a mactime timeline
- Run the workflow only against a read-only image, never the live filesystem, so you never overwrite the evidence you're recovering
Why deleted is not gone
Frame recovery on the fact that unlinking removes a name and frees an inode but leaves the data blocks intact.
In ext4 & XFS Internals for Responders you separated the two things a "file" really is: the inode (the real object — size, owner, timestamps, and the list of data blocks holding the content) and the directory entry (just a name pointing at an inode number). Deleting a file is far less destructive than the word suggests. When you
rma file, the kernel does three small bookkeeping moves:1. It removes the directory entry — the name disappears from the folder listing.
2. It marks the inode as free in the inode bitmap.
3. It marks the file's data blocks as free in the block bitmap.
Notice what it does not do: it never wipes the data blocks themselves. The bytes of the file are still sitting on the platter or flash, untouched. They're merely flagged as available for reuse. Until the filesystem happens to allocate those same blocks to a new file and write over them, the original content is fully recoverable.
Think of a contact you delete from your phone. The name vanishes from your contacts list, and the phone marks that slice of storage as reusable — but the actual digits sit in memory, perfectly intact, until something else needs the space and overwrites them. "Deleted" means forgotten and reclaimable, not erased. Forensic recovery is the art of reading those forgotten-but-still-present blocks before they're reused.
Everything in this lesson runs against the forensic image you produced in Evidence Acquisition — a bit-for-bit copy of the disk, hashed and verified, that you treat as read-only. The Sleuth Kit (TSK), the suite you'll use, reads filesystem structures directly out of an image file, without mounting it: it never triggers a journal replay, never updates an atime, never allocates a block. It reads the raw bytes and interprets the on-disk structures itself.
That read-only discipline is what protects the freed-but-intact data above. The instant a filesystem is mounted read-write and anything is written to it, the allocator may hand a freed block to a new file — overwriting the deleted evidence you came to recover. By analyzing an image (and keeping the original drive powered off or write-blocked), you guarantee the blocks stay frozen exactly as they were at acquisition.
Check yourself
After
rm secret.tar, the directory entry is gone and the inode is marked free. What happened to the file's actual data blocks?Find the partitions: mmls
Read the partition layout of a whole-disk image so later tools know where the filesystem starts.
A whole-disk image isn't one filesystem — it's a partition table followed by one or more partitions, each with its own filesystem. TSK's filesystem tools (
fls,istat,icat) need to know where the partition begins, measured as an offset in sectors from the start of the image.mmls("media management list") reads the partition table and prints that map. If your image is already a single partition (not a whole disk), you can skip straight tofls— but on a full disk you start here.Example — not runnable on your lab box
mmls evidence.imgDOS Partition Table Offset Sector: 0 Units are in 512-byte sectors Slot Start End Length Description 000: Meta 0000000000 0000000000 0000000001 Primary Table (#0) 001: ------- 0000000000 0000002047 0000002048 Unallocated 002: 000:000 0000002048 0000204799 0000202752 Linux (0x83)- (no flag)
- Plain mmls auto-detects the partition scheme (DOS/MBR, GPT, BSD, etc.) and lists every slot.
- -t TYPE
- Force a specific table type (e.g. -t gpt) if auto-detection guesses wrong.
- -B
- Also print each partition's length in bytes, handy for sizing.
The line that matters is slot 002: a Linux (0x83)partition that starts at sector 2048. That start offset — 2048 — is the number every later TSK tool needs via-o, so it can find the filesystem inside the larger image. TheUnallocatedgap before it (sectors 0–2047) is the normal alignment padding ahead of the first partition. Note units are 512-byte sectors: the offset is counted in sectors, not bytes.Check yourself
Which TSK tool reads the partition table of a whole-disk image and gives you the sector offset where a partition starts?
List files, including deleted: fls
List directory contents — including entries the filesystem marked deleted — and read the inode numbers.
fls("file list") lists the files and directories in the filesystem at the offset you give it — and crucially, it shows deleted entries too, because it reads the directory structure directly rather than asking the kernel for a clean listing. A deleted entry is flagged with a leading *`, and each line ends with the entry's inode number**, which is the key you'll use to recover its content.Run against the partition offset from
mmls,flsbecomes your map of what's there — and what was there and got deleted but still has a recoverable inode.Example — not runnable on your lab box
fls -o 2048 -r evidence.imgd/d 11: lost+found d/d 12: home + d/d 261: home/webadmin + r/r 519: home/webadmin/.bash_history + r/r 520: home/webadmin/notes.txt + * r/r 524: home/webadmin/exfil.tar.gz d/d 13: var + d/d 1041: var/log + r/r 1190: var/log/auth.log + * r/r 1192: var/log/auth.log.1
- -o 2048
- Filesystem starts at sector 2048 (the offset from mmls). Required on a whole-disk image.
- -r
- Recurse into subdirectories so you get the whole tree, not just the top level.
- -d
- Show ONLY deleted entries — a fast way to surface just the * lines on a noisy disk.
- -p
- Print full paths (prefix each name with its directory) instead of the indented tree.
Read the markers. r/ris a regular file,d/da directory. The leading *`** flags a **deleted** entry:r/r 524: home/webadmin/exfil.tar.gz` is a deleted file whose name and inode 524 are still readable in the directory structure. Same story for ` r/r 1192: var/log/auth.log.1— a rotated log the attacker deleted. The **number after the type** (524,1192) is the **inode**, and that number — not the filename — is what you hand toistatandicatnext. The+` characters just show tree depth from the recursion.Check yourself
In
flsoutput, what does a leading*in front of an entry tell you?Inspect the inode: istat
Read an inode's metadata and the list of data blocks that hold the file's content.
You have a deleted file's inode number from
fls(524).istat("inode stat") prints everything that inode records: ownership, permissions, size, the full set of timestamps, and — the part recovery hinges on — the list of data blocks the file occupied. For a freed inode whose block list hasn't been overwritten, that list is your treasure map: it tells you exactly which blocks on disk still hold the file's bytes.Example — not runnable on your lab box
istat -o 2048 evidence.img 524inode: 524 Not Allocated UID / GID: 1000 / 1000 Mode: rrw-r--r-- Size: 24576 Num of Links: 0 Inode Times: Accessed: 2026-06-23 02:31:09 (UTC) File Modified: 2026-06-23 02:30:55 (UTC) Inode Modified: 2026-06-23 02:33:12 (UTC) Deleted: 2026-06-23 02:33:12 (UTC) Direct Blocks: 8456 8457 8458 8459 8460 8461
- -o 2048
- Same partition offset from mmls — istat needs to know where the filesystem lives.
- 524
- The inode number (from fls), NOT a filename. istat addresses files by inode.
- -z ZONE
- Interpret the timestamps in a given timezone instead of the default.
Several recovery signals at once. `Not Allocated` and `Num of Links: 0` confirm this inode was freed — i.e. the file was deleted. The `Deleted:` timestamp (02:33:12) even tells you when. `Size: 24576` is 24 KB, which matches the six `Direct Blocks` (8456–8461) listed below — 6 blocks of 4 KB each. That block list is the prize: it names the exact disk blocks still holding the file's content. As long as nothing has reused them, icatcan stream those blocks straight back into a recovered file.Check yourself
Beyond timestamps and ownership, what does
istatgive you that makes block-list recovery of a deleted file possible?Recover the content: icat
Stream a deleted file's content out by inode number and verify the recovered bytes with a hash.
icat("inode cat") is the recovery tool. Give it the image, the offset, and an inode number, and it reads that inode's block list and streams the file's content to standard output — exactly likecat, but addressing the file by inode instead of by name. That distinction is the whole reason it works on deleted files: the name is gone from the directory, but the inode (524) and its blocks are still there, soicatreaches the data the only way that's left.Redirect that stream into a file and you've recovered the deleted artifact.
Example — not runnable on your lab box
icat -o 2048 evidence.img 524 > recovered_exfil.tar.gz- -o 2048
- The partition offset from mmls.
- 524
- The inode to recover — by NUMBER. icat does not take a filename; the name is gone.
- -r
- Recovery mode: for a deleted file, try harder to follow the freed inode's block pointers.
- > recovered_exfil.tar.gz
- Shell redirect — capture the streamed content into a new file in your working directory.
icatprints nothing to the terminal (its output is the raw file bytes, which you redirected intorecovered_exfil.tar.gz). It walked inode 524's block list — blocks 8456–8461 fromistat— and wrote those 24 KB back out. The deleted exfil archive now exists again as a normal file you can examine, all without ever touching the original evidence image, which TSK only ever read.Example — not runnable on your lab box
file recovered_exfil.tar.gz && sha256sum recovered_exfil.tar.gzrecovered_exfil.tar.gz: gzip compressed data, last modified: ... 3b1f9c0a7e2d... recovered_exfil.tar.gz
Always verify a recovery, never assume it. fileconfirms the bytes are a real gzip archive — proof the block list was intact andicatreconstructed a valid file, not a garbled fragment from reused blocks.sha256sumgives you a fingerprint to record in your notes and to compare if you recover the same artifact by another method. A recovered file that won't open, or whosefiletype is nonsense, is your signal that some of its blocks were already overwritten.Check yourself
Which Sleuth Kit tool streams a file's content using its inode number — letting you recover a deleted file whose name is gone?
Inventory for a timeline: fls -m
Produce a body file of every file's metadata and feed it to mactime for a timeline.
Recovering one file answers "what was deleted here." The bigger forensic question is "what happened, in what order."
fls -mproduces a body file: a machine-readable inventory of every file and inode — allocated and deleted — with its size, ownership, and all of its MAC timestamps (Modified, Accessed, Changed/inode-changed, and birth). It's the raw material for a timeline.Feed that body file to
mactimeand it sorts every timestamp from every file into a single chronological list — a minute-by-minute reconstruction of filesystem activity. This is the same timeline idea from the file-forensics work, but built from the image and including deleted entries, so an attacker's create-use-delete sequence shows up as a clean run of events.Example — not runnable on your lab box
fls -o 2048 -r -m / evidence.img > bodyfile.txt- -m /
- Emit body-file format, prefixing paths with the mount point (here '/'), instead of the human listing.
- -r
- Recurse the whole tree so every file's metadata is captured.
- -o 2048
- The partition offset from mmls.
- > bodyfile.txt
- Save the body file; it's intermediate input for mactime, not meant to be read directly.
This writes a pipe-delimited record per file into bodyfile.txt— one line each for the deletedexfil.tar.gz, the rotatedauth.log.1, and every other inode, each carrying its four timestamps. There's no terminal output; the body file is the deliverable. On its own it's hard to read, which is exactly whatmactimeis for.mactime -b bodyfile.txt -d 2026-06-23 > timeline.csv- -b bodyfile.txt
- Read this body file (the output of fls -m) as input.
- -d
- Output in comma-delimited (CSV) form for spreadsheets or further tooling.
- 2026-06-23
- Optional start date — only show activity from this date forward to focus the window.
mactimecollapses every file's M/A/C/B times into one time-ordered table: at each timestamp, which files were modified, accessed, inode-changed, or born. Read top to bottom, the attacker's footprint reads like a story — the staging archive created, written, then deleted at 02:33; the log rotated and removed minutes later. Because the body file came fromfls(which sees deleted entries), those deleted artifacts appear in the timeline rather than vanishing from it.Try it in your live terminal
Run the recovery workflow yourself — robustly, whether or not TSK is installed.
You have a real root shell. The Sleuth Kit isn't installed on every box, so this sequence checks for it first and gives you a portable fallback if it's missing. Everything here is read-only — you're listing and inspecting, not writing to any live filesystem. If you have a practice image handy, point the TSK commands at it; otherwise the checks and the
debugfsfallback work on the box as-is.command -v fls || echo "Sleuth Kit not installed here — install with: sudo apt-get install -y sleuthkit (Debian) / sudo dnf install -y sleuthkit (RHEL)"Probe for TSK before relying on it. If flsexists,command -vprints its path and you can run the real workflow below. If not, you get the exact install command for Debian/Ubuntu and RHEL/Fedora. Nothing here changes the system.Example — not runnable on your lab box
mmls evidence.img; echo '--- then list files incl. deleted ---'; fls -o 2048 -r evidence.img | grep '\*'IF TSK is present and you have an image: read the partition offset with mmls, then list the tree withflsandgrep '\*'to surface only the deleted entries. Note the inode number on any line you care about — that's your input to istat and icat.istat -o 2048 evidence.img <INODE> # then: icat -o 2048 evidence.img <INODE> > /tmp/recovered.binIF TSK is present: swap <INODE>for a deleted inode from the fls output.istatshows its block list (sanity-check that the block count matches the size);icatstreams the content into a recovered file. Verify it afterward withfile /tmp/recovered.bin. icat takes the inode NUMBER, never a filename.sudo debugfs -R 'lsdel' "$(findmnt -no SOURCE /)" 2>/dev/null | headPortable fallback when TSK is absent: debugfsships with e2fsprogs on virtually every ext-based Linux box.-R 'lsdel'lists recently deleted inodes on the device backing/(resolved withfindmnt), showing inode number, owner, size, and deletion time — the same leadfls/istatgive you. This is read-only;debugfsopens the filesystem read-only by default unless you pass-w. From anlsdelinode you coulddebugfs -R 'dump <inode> /tmp/out'to recover content — the debugfs analogue of icat.
- Deleting a file removes its name and frees its inode and blocks, but does not erase the data — the bytes survive until the blocks are reused, which is what makes recovery possible.
- Always work from a read-only image, never the live filesystem. A single write to a mounted compromised disk can overwrite the freed blocks you came to recover.
- The TSK workflow is layered:
mmlsfinds the partition offset,flslists files (deleted ones flagged*) and gives you inode numbers,istatshows the inode's block list,icatstreams the content back by inode. icataddresses files by inode number, not name — that's exactly why it can recover a file whose directory entry is gone. Always verify a recovery withfileand a hash.fls -mbuilds a body file thatmactimeturns into a timeline that includes deleted entries — reconstructing the attacker's create-use-delete sequence in order.
| Tool | Layer | What it gives you |
|---|---|---|
mmls | Partition table | The sector offset where each partition's filesystem starts (the -o value) |
fls | Directory / file names | Listing including deleted entries (marked *) and each entry's inode number |
istat | Inode metadata | Ownership, timestamps, deletion time, and the data-block list for one inode |
icat | File content | The file's bytes streamed by inode number — the actual recovered data |
fls -m → mactime | Timeline | A body file turned into a chronological MAC timeline, including deleted files |
debugfs -R lsdel (fallback) | Deleted inodes | Recently deleted inodes on an ext filesystem when TSK isn't installed |
