Forensic recovery from filesystem slack space and swap space — two locations that file-level tools cannot see. Slack lives at the end of every allocated file’s last block; swap holds passively-evicted RAM pages. Both contain forensically valuable artefacts and neither shows up in ls, find, or any stat-based view. Companion to File Carving for the unallocated-region recovery story.

Side: blue


File slack — concept

A file’s data occupies whole filesystem blocks. The last block almost always has unused bytes between the end of file data and the block boundary. Anything written there is invisible to filesystem tools — md5sum reads only 0..size-1 bytes, never reaching slack.

[ block N-1 ][ block N (last) ][        ]
              ^- file data    ^- slack space (zeroed at allocation,
                                  but may contain residual deleted-file data
                                  or deliberately-hidden payloads)

Slack size formula:

slack_size      = block_size - (file_size MOD block_size)
slack_byte_offset_in_disk = last_block_number * block_size + (file_size MOD block_size)

Block size is reported by stat as IO Block. Last allocated block comes from debugfs (see Filesystem Metadata).


Calculate slack offset

# 1. file size and FS block size
stat target.jpg
#   Size: 58178    Blocks: 120    IO Block: 4096
#   Inode: 789983
 
echo $((58178 % 4096))                                    # 834  - bytes occupied in last block
echo $((4096 - 58178 % 4096))                             # 3262 - bytes of slack available
 
# 2. find the actual disk blocks
df target.jpg                                             # which device, e.g. /dev/sda1
sudo debugfs -R "stat <789983>" /dev/sda1 | cat
#   EXTENTS: (0-14):12156982-12156996
# block 12156996 is the last allocated block; it contains the slack
 
# 3. compute the slack-start byte offset on the raw device
echo $((12156996 * 4096 + 834))
# = 49775083866  (this is the first byte of slack in /dev/sda1)

debugfs is piped through cat because it would otherwise enter interactive mode. Always include the pipe.


Read slack contents

# read 4 KiB starting at slack offset
sudo xxd -s $((12156996 * 4096 + 834)) -l 4096 /dev/sda1 | head
 
# verify a known footer immediately precedes slack (sanity check before writing)
sudo xxd -s $((12156996 * 4096 + 832)) -l 16 /dev/sda1
# expected: ffd9 0000 0000 ...   (JPEG EOI followed by zero-filled slack)

Findings:

Slack contentMeaning
All-zeroclean — no residual data, no hiding
Non-zero binary that is not file-data continuationresidual from a previously-deleted file that occupied the same block, OR deliberately written payload
Recognisable file fragment (PE header, JPEG header, ASCII text)almost certainly residual or planted
Footer-then-zeros-then-contenta payload was deliberately written into slack after the file was created

Block-level extraction with dd

# extract slack region as a separate file for analysis
sudo dd if=/dev/sda1 of=slack.bin \
        bs=1 \
        skip=$((12156996 * 4096 + 834)) \
        count=$((4096 - 58178 % 4096))
 
file slack.bin
strings slack.bin

Use bs=1 for byte-precision skip and count. Slow but accurate. For larger ranges, switch to block-aligned extraction with bigger bs and adjust skip / count accordingly.

Demonstrating slack persistence
# write a known string to slack
echo "marker text" | sudo dd of=/dev/sda1 bs=1 seek=$((12156996 * 4096 + 834))
 
# host file hash unchanged (slack lives outside the file boundary)
md5sum target.jpg                                          # same hash as before
 
# slack now holds the marker
sudo xxd -s $((12156996 * 4096 + 834)) -l 32 /dev/sda1
# 6d61 726b 6572 2074 6578 740a ...   "marker text\n..."

This pattern demonstrates why slack-space hiding works: filesystem reads stop at the recorded size; raw block-device reads do not.

On evidence: never write to evidence. The pattern above is for understanding only — operationally you image the disk first, then read slack from the image.


Swap space analysis

Swap holds RAM pages evicted under memory pressure. A browser with many tabs, a clipboard manager, or any long-running process leaves recoverable artefacts in swap: URLs, page content, JSON, copied passwords, document fragments.

First-pass: strings
strings -n 8 swap.dump > swap.strings                     # min length 8, save full text dump
grep -i -E '(http|https|ftp)://[a-z0-9.-]+/' swap.strings | sort -u
grep -i -E '@[a-z0-9.-]+\.(com|net|org|io)' swap.strings | sort -u   # email addresses
grep -i -E '\b[A-Fa-f0-9]{32,}\b' swap.strings | sort -u             # MD5 / SHA candidates
swap_digger

Categorised first-pass swap analysis. Web credentials, email content, wifi credentials, URLs, hashes.

git clone https://github.com/sevagas/swap_digger.git
cd swap_digger
 
sudo ./swap_digger.sh -a -l -s /full/path/to/swap.dump
FlagPurpose
-psystem passwords from /etc/shadow patterns
-gguess passwords from observations (high false-positive rate)
-aextended tests — web passwords, emails, wifi creds, URLs, hashes. Default for forensic analysis
-lwrite timestamped log of all stdout. Always use — categorised sections only appear in stdout, not in saved files
-s <path>swap device or dump file (must be absolute path)
-r <root>root path of target filesystem (when cross-referencing /etc/shadow)
-cauto-erase working directory after completion (skip during investigation)
Pitfalls
  • swap_digger caches in /tmp/swap_dig/. Re-runs return cached results unless you delete the cache or use -c.
  • swap_digger’s URL section uses sort | uniq -cd which only emits URLs seen more than once. One-off URLs are filtered out — also grep the raw swap_dump.txt for full coverage.
  • swap_digger fails silently on relative paths. Always use absolute paths.
  • Password output has high false-positive rate. Cross-reference with URL/email findings before treating as credentials.

Forensic interpretation

FindingWhat it tells you
ASCII text in slackresidual deleted-file content OR deliberate hiding
File-format header in slacka different file once occupied the same block; recover with header/footer carving
Multiple URLs in swap from one domainhost fetched that resource recently; correlate with browser history
Password-like strings near payload URLs in swaplikely captured during exfil prep; treat as compromised credential
Clipboard manager artefacts in swapuser copied that content into a clipboard buffer at some point
Process command lines in swaprecovers attacker shell sessions even after the process exited

Pitfalls

  • Slack offset arithmetic uses last_block * block_size + (size MOD block_size). The most common error is multiplying by sector size (512) instead of block size (typically 4096).
  • debugfs against a mounted filesystem can return stale data. Always read against an image.
  • bs=1 is byte-precise but slow. For multi-GB extractions, segment the read with block-aligned bs and adjust offsets.
  • strings default minimum length 4 produces enormous output on large swap files. Use -n 8 or higher to filter noise.
  • Swap is overwritten as the system runs. The artefacts you recover are a snapshot — gaps are normal and do not mean nothing happened.
  • Encrypted swap (LUKS, dm-crypt) returns garbage. Confirm swapon --show plaintext before investing analysis time.

Field Manual | Data Recovery | File Carving | Filesystem Metadata