Signal9Signal9 — home
online
all lessons

Artifact & Deleted-File Recovery with The Sleuth Kit

Advanced40 min7 steps
What you'll learn
  • 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
  1. 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 rm a 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?

  2. 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 to fls — but on a full disk you start here.

    Example — not runnable on your lab box

    mmls evidence.img
    DOS 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. The Unallocated gap 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?

  3. 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, fls becomes 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.img
    d/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/r is a regular file, d/d a 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 to istat and icat next. The +` characters just show tree depth from the recursion.

    Check yourself

    In fls output, what does a leading * in front of an entry tell you?

  4. 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 524
    inode: 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, icat can stream those blocks straight back into a recovered file.

    Check yourself

    Beyond timestamps and ownership, what does istat give you that makes block-list recovery of a deleted file possible?

  5. 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 like cat, 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, so icat reaches 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.
    icat prints nothing to the terminal (its output is the raw file bytes, which you redirected into recovered_exfil.tar.gz). It walked inode 524's block list — blocks 8456–8461 from istat — 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.gz
    recovered_exfil.tar.gz: gzip compressed data, last modified: ...
    3b1f9c0a7e2d... recovered_exfil.tar.gz
    Always verify a recovery, never assume it. file confirms the bytes are a real gzip archive — proof the block list was intact and icat reconstructed a valid file, not a garbled fragment from reused blocks. sha256sum gives 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 whose file type 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?

  6. 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 -m produces 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 mactime and 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 deleted exfil.tar.gz, the rotated auth.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 what mactime is 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.
    mactime collapses 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 from fls (which sees deleted entries), those deleted artifacts appear in the timeline rather than vanishing from it.
  7. 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 debugfs fallback 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 fls exists, command -v prints 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 with fls and grep '\*' 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.bin
    IF TSK is present: swap <INODE> for a deleted inode from the fls output. istat shows its block list (sanity-check that the block count matches the size); icat streams the content into a recovered file. Verify it afterward with file /tmp/recovered.bin. icat takes the inode NUMBER, never a filename.
    sudo debugfs -R 'lsdel' "$(findmnt -no SOURCE /)" 2>/dev/null | head
    Portable fallback when TSK is absent: debugfs ships with e2fsprogs on virtually every ext-based Linux box. -R 'lsdel' lists recently deleted inodes on the device backing / (resolved with findmnt), showing inode number, owner, size, and deletion time — the same lead fls/istat give you. This is read-only; debugfs opens the filesystem read-only by default unless you pass -w. From an lsdel inode you could debugfs -R 'dump <inode> /tmp/out' to recover content — the debugfs analogue of icat.
Recap
  • 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: mmls finds the partition offset, fls lists files (deleted ones flagged *) and gives you inode numbers, istat shows the inode's block list, icat streams the content back by inode.
  • icat addresses files by inode number, not name — that's exactly why it can recover a file whose directory entry is gone. Always verify a recovery with file and a hash.
  • fls -m builds a body file that mactime turns into a timeline that includes deleted entries — reconstructing the attacker's create-use-delete sequence in order.
TSK tool → layer → what it recovers
ToolLayerWhat it gives you
mmlsPartition tableThe sector offset where each partition's filesystem starts (the -o value)
flsDirectory / file namesListing including deleted entries (marked *) and each entry's inode number
istatInode metadataOwnership, timestamps, deletion time, and the data-block list for one inode
icatFile contentThe file's bytes streamed by inode number — the actual recovered data
fls -m → mactimeTimelineA body file turned into a chronological MAC timeline, including deleted files
debugfs -R lsdel (fallback)Deleted inodesRecently deleted inodes on an ext filesystem when TSK isn't installed