DELIGHT Cybersecurity Workbook Series

Digital Forensics Hands-On Labs

Enter the access code provided by your instructor to open this workbook.

Hands-On Edition · 20 Labs

Run Every Core Forensics Technique Yourself

Twenty self-contained lab exercises across foundations, disk forensics, memory forensics, and network forensics. Every lab builds its own evidence first (so you're not hunting for sample files), then walks through the exact commands to analyse it, with expected output and a checkpoint to confirm you got the right result.

Platform
Ubuntu or Kali Linux VM
Privileges
sudo access
Disk space
~5 GB free
Network
Internet for package install only
01

Forensic Foundations

Four labs covering the procedural rules that make every later lab's results trustworthy: hashing, write-blocking, volatility ordering, and chain-of-custody documentation.

Verify Evidence Integrity with Hashing

Foundations LAB 1.1
Time: 15 min Tools: sha256sum, md5sum, xxd
Generate cryptographic hashes for a piece of evidence, prove a one-byte change is detectable, and produce a hash manifest like the one attached to real evidence on intake.
1
Create a working evidence folder and a sample file

Set up a dedicated case folder so nothing in this lab touches your real files.

bash
mkdir -p ~/dfir/lab1.1 && cd ~/dfir/lab1.1
echo "Suspect access log entry: user=admin login=success ip=10.0.0.15" > evidence.txt
cat evidence.txt
2
Hash the file with both MD5 and SHA-256

Record both — MD5 for legacy compatibility, SHA-256 as the case standard.

bash
md5sum evidence.txt
sha256sum evidence.txt
Expected output
Two lines, each showing a long hex string followed by evidence.txt. Your exact hashes will differ from anyone else's — that's expected, since the file content (and any trailing newline) determines the hash.
3
Save the hashes as your acquisition manifest
bash
sha256sum evidence.txt > evidence.sha256
cat evidence.sha256
4
Tamper with a copy and re-verify

Make a copy, change a single character inside it, then check it against the original manifest.

bash
cp evidence.txt evidence_tampered.txt
sed -i 's/admin/ADMIN/' evidence_tampered.txt

# Verify the ORIGINAL against its manifest -- should pass
sha256sum -c evidence.sha256

# Now hash the tampered copy and compare manually
sha256sum evidence_tampered.txt
Expected output
sha256sum -c on the original prints evidence.txt: OK. The tampered file's hash is completely different from the original despite only a 5-character change (adminADMIN) — this is the avalanche effect.
Checkpoint

Mount Evidence Read-Only (Software Write Blocking)

Foundations LAB 1.2
Time: 20 min Tools: dd, mount, losetup, mkfs.ext4
Build a small "evidence" disk image, mount it read-only, prove that write attempts are rejected, and confirm the image hash never changes — exactly the guarantee a hardware write blocker provides.
1
Create a small disk image and format it

This 50MB file will stand in for a "seized drive."

bash
mkdir -p ~/dfir/lab1.2 && cd ~/dfir/lab1.2
dd if=/dev/zero of=evidence.img bs=1M count=50
mkfs.ext4 -F evidence.img
sha256sum evidence.img > evidence.img.sha256
2
Mount it normally and add a "suspect" file

This represents the original drive in use, before it becomes evidence.

bash
mkdir -p mnt
sudo mount -o loop evidence.img mnt
echo "original suspect file" | sudo tee mnt/notes.txt
sudo umount mnt
3
Re-hash, then mount read-only as you would real evidence
bash
# This is now your "acquired" image -- hash it for the manifest
sha256sum evidence.img > acquired.sha256

# Mount with the read-only flag, just like a write-blocked drive
sudo mount -o loop,ro evidence.img mnt
mount | grep evidence.img
Expected output
The mount line for evidence.img includes ro in its options list, e.g. (ro,relatime).
4
Attempt to write to the mounted evidence — and confirm it fails
bash
echo "tampering attempt" | sudo tee mnt/notes.txt
touch mnt/newfile.txt
Expected output
Both commands fail with Read-only file system. The original notes.txt content is untouched.
5
Unmount and re-verify the hash hasn't changed
bash
sudo umount mnt
sha256sum -c acquired.sha256
Expected output
evidence.img: OK — the hash matches even after mounting and attempted writes, proving the read-only mount preserved the evidence.
Checkpoint

Capture Evidence in Order of Volatility

Foundations LAB 1.3
Time: 15 min Tools: ss, ps, free, w, last
Practice triaging a "live" system by collecting volatile evidence in the correct order — most volatile first — using only built-in commands, before anything is shut down or imaged.
1
Create a capture folder for this triage
bash
mkdir -p ~/dfir/lab1.3 && cd ~/dfir/lab1.3
2
Capture network state first (most volatile after RAM itself)

Active connections and the routing/ARP tables vanish quickly — capture these before anything else.

bash
ss -tunap > 01_network_connections.txt
ip route > 02_routing_table.txt
ip neigh > 03_arp_cache.txt
3
Capture running processes
bash
ps auxf > 04_process_tree.txt
free -h > 05_memory_usage.txt
4
Capture logged-in users and recent login history

This is less volatile — it's backed by log files on disk — but still time-sensitive if an attacker is actively logged in.

bash
w > 06_logged_in_users.txt
last -n 20 > 07_recent_logins.txt
5
Hash every capture file and timestamp the collection

Even quick triage captures need integrity hashes and a recorded collection time.

bash
date -u > 00_collection_timestamp_UTC.txt
sha256sum *.txt > manifest.sha256
cat manifest.sha256
Expected output
A manifest listing eight files (00 through 07) with hashes, in the order they were collected — most volatile data captured first.
Checkpoint

Build a Chain-of-Custody Log

Foundations LAB 1.4
Time: 15 min Tools: bash, sha256sum
Simulate evidence moving through three handlers (collector, analyst, reviewer), logging each transfer with timestamps and hashes in a CSV chain-of-custody file.
1
Create the "evidence" item
bash
mkdir -p ~/dfir/lab1.4 && cd ~/dfir/lab1.4
echo "Item: USB-001 -- 16GB USB drive recovered from suspect desk" > item_USB-001.txt
HASH=$(sha256sum item_USB-001.txt | awk '{print $1}')
echo "Initial hash: $HASH"
2
Initialize the chain-of-custody log
bash
echo "timestamp_utc,item_id,action,from,to,sha256" > coc_log.csv

echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),USB-001,COLLECTED,Crime Scene,Investigator A,$HASH" >> coc_log.csv
cat coc_log.csv
3
Log a transfer to the analyst, then verify the hash before logging

In real casework, you re-hash before every transfer to confirm nothing changed while in the previous handler's custody.

bash
sleep 2
CURRENT_HASH=$(sha256sum item_USB-001.txt | awk '{print $1}')

if [ "$CURRENT_HASH" == "$HASH" ]; then
  echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),USB-001,TRANSFERRED,Investigator A,Analyst B,$CURRENT_HASH" >> coc_log.csv
  echo "Hash verified -- transfer logged"
else
  echo "HASH MISMATCH -- DO NOT LOG TRANSFER, FLAG FOR REVIEW"
fi
4
Simulate an undocumented modification, then attempt another transfer
bash
# Someone edits the file without updating the chain of custody
echo "unauthorized note added" >> item_USB-001.txt

CURRENT_HASH=$(sha256sum item_USB-001.txt | awk '{print $1}')
if [ "$CURRENT_HASH" == "$HASH" ]; then
  echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),USB-001,TRANSFERRED,Analyst B,Reviewer C,$CURRENT_HASH" >> coc_log.csv
  echo "Hash verified -- transfer logged"
else
  echo "HASH MISMATCH -- DO NOT LOG TRANSFER, FLAG FOR REVIEW"
fi

cat coc_log.csv
Expected output
The second transfer attempt prints HASH MISMATCH -- DO NOT LOG TRANSFER, FLAG FOR REVIEW and is not appended to coc_log.csv. The log now shows only two valid entries (COLLECTED, TRANSFERRED) — exactly the gap a defense attorney would seize on.
Checkpoint
02

Disk & File System Forensics

Five labs: image a disk, recover deleted files, carve files by signature from a damaged file system, pull metadata plus hidden steganographic data out of an image file, and use MACB timestamps to reconstruct a ransomware-style mass-encryption timeline.

Create and Verify a Forensic Disk Image

Disk LAB 2.1
Time: 20 min Tools: dd, losetup, sha256sum, file
Build a small "source drive" with files on it, image it bit-for-bit with dd, hash both the source and the image, and confirm the image is forensically identical.
1
Build a virtual "source drive" using a loop device
bash
mkdir -p ~/dfir/lab2.1 && cd ~/dfir/lab2.1

# Create a 100MB blank container and format it
dd if=/dev/zero of=source_drive.img bs=1M count=100
mkfs.ext4 -F source_drive.img

# Attach it as a loop device
sudo losetup -fP source_drive.img
losetup -a
Expected output
losetup -a shows an entry like /dev/loop0: ... source_drive.img. Note the device name — you'll use it next.
2
Mount it, add some files, then unmount

Replace /dev/loop0 below with whatever device name appeared in step 1.

bash
mkdir -p mnt
sudo mount /dev/loop0 mnt
echo "Quarterly report draft" | sudo tee mnt/report.txt
echo "Confidential client list" | sudo tee mnt/clients.txt
sudo umount mnt
3
Hash the source, then create a forensic image with dd
bash
# Hash the source device BEFORE imaging
sudo sha256sum /dev/loop0 | tee source.sha256

# Image it bit-for-bit (note: source and dest must differ!)
sudo dd if=/dev/loop0 of=case01_image.dd bs=4M status=progress conv=sync,noerror

# Hash the resulting image
sha256sum case01_image.dd | tee image.sha256
Expected output
The hash in source.sha256 and image.sha256 should be identical — confirming the image is a perfect bit-for-bit copy of the source device.
4
Detach the loop device and confirm the image is independently mountable
bash
sudo losetup -d /dev/loop0

# Mount the IMAGE FILE itself, read-only -- never the original again
sudo mount -o loop,ro case01_image.dd mnt
ls -la mnt
cat mnt/report.txt
sudo umount mnt
Expected output
ls shows report.txt and clients.txt inside the image, and cat prints "Quarterly report draft" — proving the image preserved the full file system, not just raw bytes you can't use.
Checkpoint

Recover Deleted Files from Unallocated Space

Disk LAB 2.2
Time: 25 min Tools: testdisk / photorec, debugfs
Delete files from a disk image, then recover them using PhotoRec — and separately inspect an ext4 inode to see why "deleted" data is still physically present.
Install required tool

This lab needs testdisk (which includes PhotoRec): sudo apt install testdisk -y

1
Build an image containing files, then delete them
bash
mkdir -p ~/dfir/lab2.2 && cd ~/dfir/lab2.2

dd if=/dev/zero of=disk.img bs=1M count=64
mkfs.ext4 -F disk.img
sudo losetup -fP disk.img
DEV=$(losetup -j disk.img | cut -d: -f1)
echo "Loop device: $DEV"

mkdir -p mnt
sudo mount $DEV mnt

# Create three files
echo "Internal memo: layoffs planned for Q3" | sudo tee mnt/memo.txt
printf '\xFF\xD8\xFF\xE0\x00\x10JFIF_fake_jpeg_data_for_lab_purposes_only' | sudo tee mnt/photo.jpg > /dev/null
echo "Password list: admin:hunter2" | sudo tee mnt/secrets.txt

ls mnt/

# Now delete them all
sudo rm mnt/memo.txt mnt/photo.jpg mnt/secrets.txt
ls mnt/

sudo umount mnt
sudo losetup -d $DEV
Expected output
The first ls mnt/ shows three files; the second shows an empty directory. The data is still on disk — only the directory entries are gone.
2
Run PhotoRec against the image to recover the deleted files

PhotoRec is interactive. Run it, then follow the on-screen prompts: select disk.img, choose the partition (or "Whole disk"), select the file system type closest to ext2/ext3/ext4, and choose a recovery destination directory (create recovered/ first).

bash
mkdir -p recovered
sudo photorec disk.img
Expected output
PhotoRec reports files recovered into subfolders like recup_dir.1/ inside your chosen destination. You should find at least the .jpg (PhotoRec carves by signature) — text files may or may not be recovered depending on whether ext4's journal still references them.
3
Inspect what "deletion" actually did at the inode level
bash
# debugfs lets you browse a filesystem image read-only without mounting
sudo debugfs -R "lsdel" disk.img
Expected output
A list of deleted inodes with their original size and deletion time — proof the metadata records of your three deleted files still exist on disk, separate from the directory listing.
Checkpoint

File Carving by Signature

Disk LAB 2.3
Time: 20 min Tools: xxd, foremost, binwalk
Build a raw blob containing multiple file types concatenated together with no file system at all, then carve the individual files back out using only their byte signatures.
Install required tools

sudo apt install foremost binwalk -y

1
Inspect file signatures (magic bytes) of real files
bash
mkdir -p ~/dfir/lab2.3 && cd ~/dfir/lab2.3

# Create a tiny real PNG using ImageMagick (or use any PNG you have)
sudo apt install -y imagemagick 2>/dev/null
convert -size 20x20 xc:blue test.png

# View the first 16 bytes in hex
xxd test.png | head -1
Expected output
The first bytes read 8950 4e47 0d0a 1a0a — the PNG signature (89 50 4E 47 0D 0A 1A 0A) shared by every PNG file ever created.
2
Create a second file type and a "no file system" blob
bash
# A minimal valid PDF (header is enough for signature detection)
printf '%%PDF-1.4\n%% Lab test PDF content\n%%%%EOF' > test.pdf

# Concatenate PNG + PDF + PNG into one raw blob -- simulating
# a damaged file system with no directory structure left
cat test.png test.pdf test.png > carved_target.raw

ls -la carved_target.raw
3
Carve files out of the blob with foremost
bash
mkdir -p carved_output
foremost -t png,pdf -i carved_target.raw -o carved_output
ls -R carved_output
Expected output
Subfolders png/ and pdf/ inside carved_output, each containing recovered files (e.g. 00000000.png, 00000123.pdf) — extracted purely from byte patterns, with no file system involved at all.
4
Cross-check with binwalk
bash
binwalk carved_target.raw
Expected output
A table listing each detected embedded file, its byte offset within carved_target.raw, and a description (e.g. PNG image, PDF document) — confirming three entries at three different offsets.
Checkpoint

Extract Metadata and Detect Hidden Data

Disk LAB 2.4
Time: 20 min Tools: exiftool, steghide
Embed GPS and timestamp metadata into an image, extract it with exiftool, then hide a secret text file inside a JPEG with steghide and recover it.
Install required tools

sudo apt install libimage-exiftool-perl steghide imagemagick -y

1
Create an image and write fake GPS/EXIF metadata into it
bash
mkdir -p ~/dfir/lab2.4 && cd ~/dfir/lab2.4

# Create a base JPEG (steghide requires JPEG, not PNG)
convert -size 400x400 xc:gray photo.jpg

# Write GPS coordinates and a capture timestamp into its EXIF data
exiftool -GPSLatitude="51.5072" -GPSLatitudeRef="N" \
         -GPSLongitude="0.1276" -GPSLongitudeRef="W" \
         -DateTimeOriginal="2026:03:14 09:21:07" \
         -overwrite_original photo.jpg
2
Extract that metadata as an investigator would
bash
exiftool photo.jpg | grep -Ei "gps|date"
Expected output
Lines showing GPS Latitude : 51 deg 30' 25.92" N, GPS Longitude : 0 deg 7' 39.36" W, and Date/Time Original : 2026:03:14 09:21:07 — these coordinates correspond to central London.
3
Hide a secret file inside the JPEG with steghide
bash
echo "Wire transfer authorization code: 88421-XQ" > secret.txt

# Embed secret.txt into photo.jpg, encrypted with a passphrase
steghide embed -cf photo.jpg -ef secret.txt -p "lab12345"

# Note the file size barely changes
ls -la photo.jpg
4
Recover the hidden data
bash
# Without a passphrase, steghide can confirm something is embedded
steghide info photo.jpg

# With the correct passphrase, extract it
steghide extract -sf photo.jpg -p "lab12345"
cat secret.txt
Expected output
steghide info confirms an embedded file is present and shows its size. steghide extract recreates secret.txt, and cat prints the wire transfer message — recovered entirely from inside what looks like an ordinary gray JPEG.
Checkpoint

MACB Timestamp Analysis & Ransomware Simulation

Disk LAB 2.5
Time: 20 min Tools: stat, touch, find, debugfs
Observe how Modified, Accessed, and Changed (MAC) timestamps respond to different operations, then simulate a ransomware encryption pass across multiple files and use timestamps alone to reconstruct the attack window.
1
Create a file and record its baseline timestamps
bash
mkdir -p ~/dfir/lab2.5 && cd ~/dfir/lab2.5
echo "quarterly_budget.xlsx contents (simulated)" > budget.txt
stat budget.txt
Expected output
Three timestamps — Access, Modify, and Change — all set to the moment of creation, plus a Birth field if your filesystem supports it.
2
Read the file and check which timestamp moves
bash
sleep 2
cat budget.txt > /dev/null
stat budget.txt
Expected output
On many modern Linux configurations, Access may not update (relatime/noatime mount options skip it for performance). If it doesn't change, note this — it's an important caveat: "file was read" is often not provable from Access time alone on modern systems.
3
Modify the file's content and check timestamps again
bash
sleep 2
echo "amended figures" >> budget.txt
stat budget.txt
Expected output
Both Modify and Change advance to the current time (Modify because content changed; Change because the inode's size/mtime metadata changed too). Birth stays at the original creation time.
4
Change only permissions and observe Change vs Modify diverge
bash
sleep 2
chmod 600 budget.txt
stat budget.txt
Expected output
Change advances again (permissions are metadata), but Modify stays at its previous value — content didn't change, only metadata. This Modify/Change split is exactly how investigators distinguish "file content changed" from "file metadata changed" (e.g. an attacker touching permissions without editing content).
5
Simulate a ransomware encryption pass across many files

Create a set of "user documents," then simulate ransomware rewriting each one's content in a tight time window.

bash
mkdir -p documents && cd documents
for i in 1 2 3 4 5; do
  echo "Document $i - normal business content" > "file$i.docx"
  sleep 1
done
cd ..

# Let some "normal time" pass before the attack
sleep 3

# Simulate ransomware: rewrite every file's content rapidly
cd documents
for f in *.docx; do
  # Overwrite content (simulating encryption) and rename with .locked extension
  echo "ENCRYPTED-$(date +%s)-$(head -c 16 /dev/urandom | base64)" > "$f"
  mv "$f" "$f.locked"
done
cd ..
6
Reconstruct the attack window using timestamps alone
bash
# List all files sorted by modification time, with timestamps
find documents -type f -printf "%T@ %Tc %p\n" | sort -n
Expected output
All five .docx.locked files show Modify timestamps clustered within roughly one second of each other — a sharp contrast to the original creation times, which were spread out by the 1-second sleep in the loop. This clustering of modification times across many unrelated files in a tiny window is the single clearest indicator of a mass-encryption event, and is exactly the pattern timeline tools like Plaso/Timesketch are built to surface automatically.
7
Identify the encryption start time
bash
# The earliest Modify timestamp among the .locked files marks attack start
find documents -name "*.locked" -printf "%T@ %p\n" | sort -n | head -1
Expected output
The single earliest timestamp among all .locked files — in a real investigation, this value (correlated against process-creation event logs or memory artefacts from the same second) is typically where you'd start looking for the malicious process's PID.
Checkpoint
03

Memory Forensics

Five labs using Volatility 3 against memory images you capture yourself: acquisition, process/network triage, hunting for code injection, dumping artefacts back out of memory, and recovering an encryption key during its live-memory window.

Acquire a Memory Image with AVML

Memory LAB 3.1
Time: 20 min Tools: avml, sha256sum
Run a marker process so it's identifiable in memory, capture a full RAM image with AVML, hash it immediately, and confirm the marker process is present in the raw bytes.
Install AVML

AVML is a single static binary from Microsoft. Download the release for Linux:

bash
cd ~/dfir && mkdir -p lab3.1 && cd lab3.1
wget -O avml https://github.com/microsoft/avml/releases/latest/download/avml
chmod +x avml
./avml --help
1
Start a "marker" process with a unique, searchable string

This simulates the malicious or notable process you'll later find in the memory dump.

bash
# Run a process with a distinctive command-line argument, in the background
sleep 600 --DFIR-MARKER-c2server.evil.example &
echo "Marker PID: $!"
2
Acquire the full memory image

This may take a few minutes depending on how much RAM your VM has allocated.

bash
sudo ./avml memory.lime
ls -lh memory.lime
Expected output
A file memory.lime roughly the size of your VM's allocated RAM (e.g. a 2GB VM produces a file close to 2GB).
3
Hash the image immediately
bash
sha256sum memory.lime | tee memory.lime.sha256
4
Confirm the marker string is physically present in the raw dump

Before any framework parses it, the raw bytes already contain the evidence — this is what makes memory forensics possible.

bash
strings memory.lime | grep "DFIR-MARKER"
Expected output
sleep 600 --DFIR-MARKER-c2server.evil.example appears at least once — proof the command line of a running process is captured verbatim in RAM.
Checkpoint

Process and Network Triage with Volatility 3

Memory LAB 3.2
Time: 30 min Tools: Volatility 3 (pip), python3-venv
Install Volatility 3, run it against the image from Lab 3.1, and use pstree, cmdline, and netscan to find your marker process and its parent.
Install Volatility 3
bash
cd ~/dfir && mkdir -p lab3.2 && cd lab3.2
python3 -m venv volenv
source volenv/bin/activate
pip install volatility3
1
Run pstree against the memory image from Lab 3.1

Volatility 3 needs to identify the kernel/symbol layout — for a Linux image, generate a symbol table first if prompted, or pass --single-location directly. The first run takes longer as it builds caches.

bash
python3 -m volatility3.cli.vol -f ~/dfir/lab3.1/memory.lime linux.pstree
Expected output
A process tree showing PIDs and parent-child relationships. Locate the sleep process from Lab 3.1 and note its PID and PPID (parent process ID).
2
Confirm the command line for that PID
bash
python3 -m volatility3.cli.vol -f ~/dfir/lab3.1/memory.lime linux.psaux | grep -i marker
Expected output
A row showing the sleep 600 --DFIR-MARKER-c2server.evil.example command line, with its PID matching what you found in step 1.
3
List active network connections at the time of capture
bash
python3 -m volatility3.cli.vol -f ~/dfir/lab3.1/memory.lime linux.sockstat
Expected output
A list of open sockets per process at the moment of capture — including any SSH session you used to run the commands, which is itself a normal artefact you'd need to rule out during a real investigation.
4
Build a one-line triage summary
bash
echo "PID, PPID, and command line of marker process:" > triage_summary.txt
python3 -m volatility3.cli.vol -f ~/dfir/lab3.1/memory.lime linux.psaux | grep -i marker >> triage_summary.txt
cat triage_summary.txt
Checkpoint

Hunt for Suspicious Memory Regions

Memory LAB 3.3
Time: 25 min Tools: Volatility 3, /proc
Compare a process's memory mappings via /proc on a live system against what Volatility reports from your memory image, and identify which mapped regions are backed by files versus anonymous (potentially injected) memory.
1
On the live system, inspect your own shell's memory map

This shows what "normal" memory mappings look like for a legitimate process — your baseline for comparison.

bash
echo $$
cat /proc/$$/maps | head -20
Expected output
A list of memory regions, most showing a path on the right (e.g. /usr/bin/bash, /usr/lib/x86_64-linux-gnu/libc.so.6) — these are file-backed. A few lines with [heap], [stack], or no path at all are normal anonymous regions every process has.
2
Find the same process in your memory image with Volatility
bash
cd ~/dfir/lab3.2
source volenv/bin/activate

# List memory maps for all processes (this can be a large output)
python3 -m volatility3.cli.vol -f ~/dfir/lab3.1/memory.lime linux.proc.Maps --pid <PID_FROM_STEP_1>
Expected output
A similar list of mapped regions for the same PID, reconstructed entirely from the memory image — this should broadly match what /proc/PID/maps showed live, since the dump captured that process's state.
3
Identify what an injected/suspicious mapping would look like

Without real malware, document the red flags so you'd recognise them:

Reference: red flags in a maps listing
ObservationWhy it matters
Region with rwx (read+write+execute) permissionsLegitimate code is rarely both writable and executable at once — this combination is a classic injection indicator.
Executable region with no file pathNormal executable code maps back to a file on disk (the binary or a library). Anonymous executable memory often means code was written directly into RAM.
A well-known system process (e.g. bash, sshd) with an unusually large number of anonymous mappingsCould indicate a loader or injected shellcode allocated extra memory regions.
4
Search your own process's maps for any rwx regions
bash
cat /proc/$$/maps | grep rwx
Expected output
Likely no output (empty) — a normal bash shell has no rwx regions. This confirms what "clean" looks like, so a hit on a real case stands out by contrast.
Checkpoint

Recover File Contents from Memory

Memory LAB 3.4
Time: 20 min Tools: strings, grep, Volatility 3
Open a file on the live system so its contents enter the page cache, capture memory, then recover the file's text content directly from the RAM image — without ever touching the disk copy during analysis.
1
Create a "sensitive" file and read it into memory
bash
mkdir -p ~/dfir/lab3.4 && cd ~/dfir/lab3.4
echo "API_KEY=sk-DFIR-LAB-3f9a8b2c1d4e5f6a7b8c9d0e" > secret_config.env

# Read it so its contents are cached in RAM
cat secret_config.env
2
Capture memory again with AVML
bash
sudo ~/dfir/lab3.1/avml memory2.lime
sha256sum memory2.lime > memory2.lime.sha256
3
Search the raw memory image for the secret value
bash
strings memory2.lime | grep "API_KEY"
Expected output
API_KEY=sk-DFIR-LAB-3f9a8b2c1d4e5f6a7b8c9d0e — the full secret, recovered from a raw RAM dump with a basic string search, demonstrating why secrets in memory matter even if a disk is encrypted.
4
Use Volatility to list open file handles for your shell
bash
cd ~/dfir/lab3.2
source volenv/bin/activate
python3 -m volatility3.cli.vol -f ~/dfir/lab3.4/memory2.lime linux.lsof --pid $$
Expected output
A list of open file descriptors for your shell's PID — note that $$ refers to your current shell, which may differ from the shell active at capture time. If the PID doesn't match, use linux.pstree first to find the correct PID for the shell that ran step 1.
Checkpoint

Recover an Encryption Key from a Memory Dump

Memory LAB 3.5
Time: 25 min Tools: openssl, python3, AVML, strings/grep
Generate an AES key inside a running process, capture memory while the key is resident, then locate the raw key bytes in the dump — demonstrating the "window between key generation and key wipe" that makes memory-based key recovery possible.
1
Write a small script that generates and holds an AES key

The script generates a random 256-bit key, prints it (so we know what to search for), and then sleeps — keeping the key resident in its process memory the whole time.

bash
mkdir -p ~/dfir/lab3.5 && cd ~/dfir/lab3.5

cat > keyholder.py << 'EOF'
import os, time, binascii

key = os.urandom(32)  # 256-bit AES key
print("Generated key (hex):", binascii.hexlify(key).decode())
print(f"PID: {os.getpid()}")
print("Holding key in memory for 120 seconds...")
time.sleep(120)
EOF

python3 keyholder.py > keyinfo.txt &
sleep 1
cat keyinfo.txt
Expected output
A line showing Generated key (hex): <64 hex characters> and the process PID. Record the hex key value — this is your "ground truth" for the search.
2
Capture memory while the key is still resident

The script sleeps for 120 seconds, giving you a window to capture memory before it exits and the key is freed.

bash
sudo ~/dfir/lab3.1/avml memory_key.lime
sha256sum memory_key.lime > memory_key.lime.sha256
3
Search the dump for the printed hex representation of the key

Because the script printed the key as hex text to stdout, that text representation is likely cached in memory too — search for it first as a sanity check.

bash
# Extract the hex key value you recorded in step 1, then search for it
KEY_HEX=$(grep "Generated key" keyinfo.txt | awk '{print $NF}')
echo "Searching for: $KEY_HEX"

strings memory_key.lime | grep "$KEY_HEX"
Expected output
The hex string appears at least once in the dump — the printed text output was buffered in the process's memory at capture time.
4
Search for the raw binary key bytes (not just the hex text)

The actual 32 raw key bytes held by the Python key variable are a different — and more realistic — target than the printed text. Convert the hex back to raw bytes and grep the binary dump for that exact byte sequence.

bash
# Convert hex string back to raw binary, write to a small file
echo -n "$KEY_HEX" | xxd -r -p > key.bin
ls -la key.bin   # should be exactly 32 bytes

# Search the memory dump for this exact 32-byte sequence
grep -aboF "$(cat key.bin)" memory_key.lime | head -5
Expected output
At least one byte-offset match (e.g. 1483920192:...), confirming the raw 32-byte AES key — the actual key material that would be used to encrypt/decrypt data — is physically present in RAM. If no match is found, the key may have been in a CPU register or already garbage-collected at the moment of capture; note this as a real limitation of the technique.
5
Let the process exit and confirm the key disappears
bash
# Wait for the keyholder script to finish
wait

# Capture memory again now that the process has exited
sudo ~/dfir/lab3.1/avml memory_after.lime

# Search again -- this time the exact byte sequence should be much less likely to appear
grep -aboF "$(cat key.bin)" memory_after.lime | head -5
Expected output
Typically no match (or far fewer matches) — once the process exits, its memory is freed and eventually reused, illustrating the "window between key generation and key wipe": capture timing is everything in this technique.
Checkpoint
04

Network Forensics

Six labs: capture and filter your own traffic with tcpdump/Wireshark, reconstruct a full TCP conversation, simulate and detect DNS beaconing, analyse email headers for spoofing indicators, generate and correlate flow-level connection logs, and examine TLS certificates for red flags.

Capture and Filter Live Traffic

Network LAB 4.1
Time: 20 min Tools: tcpdump, Wireshark or tshark
Capture live traffic to a .pcap file while generating known HTTP and DNS activity, then apply display filters to isolate just that traffic.
Install required tools

sudo apt install tcpdump tshark curl -y (Wireshark GUI optional: sudo apt install wireshark)

1
Identify your network interface
bash
ip -brief link show
Expected output
A list of interfaces — typically eth0 or enp0s3 for the primary network adapter. Use this name in the next step.
2
Start a capture, then generate traffic in another terminal

Run the capture in one terminal (it will run for 20 seconds), and in a second terminal — opened immediately — run the curl commands.

terminal 1
mkdir -p ~/dfir/lab4.1 && cd ~/dfir/lab4.1
sudo timeout 20 tcpdump -i <your_interface> -w capture.pcap
terminal 2
curl -s http://example.com > /dev/null
nslookup example.com
3
Inspect the capture summary
bash
tshark -r capture.pcap -q -z io,phs
Expected output
A protocol hierarchy showing percentages for dns and http (or tls if example.com redirected to https) among the captured frames.
4
Apply display filters to isolate DNS and HTTP traffic
bash
# Show only DNS queries and responses
tshark -r capture.pcap -Y "dns"

# Show only HTTP requests, with method and host
tshark -r capture.pcap -Y "http.request" -T fields -e ip.dst -e http.host -e http.request.method
Expected output
The DNS filter shows the query for example.com and its A-record response. The HTTP filter shows a GET request with host example.com (if the connection wasn't upgraded to HTTPS).
Checkpoint

Reconstruct a Full TCP Conversation

Network LAB 4.2
Time: 20 min Tools: netcat, tcpdump, tshark
Capture a plaintext conversation between a local "client" and "server" using netcat, then reassemble the full TCP stream from the .pcap to read the conversation as the application saw it.
1
Start a capture on the loopback interface
terminal 1
mkdir -p ~/dfir/lab4.2 && cd ~/dfir/lab4.2
sudo tcpdump -i lo -w stream.pcap port 9999
2
Start a netcat "server" listening on port 9999
terminal 2
nc -l -p 9999
3
Connect as a "client" and exchange messages

In a third terminal, connect and type a message. Switch to terminal 2 and type a reply. Then close both with Ctrl+C and stop the tcpdump in terminal 1 with Ctrl+C.

terminal 3
nc 127.0.0.1 9999
# Type: USER admin
# Type: PASS S3cr3tPass!
terminal 2 (reply)
# Type: 230 Login successful
4
Reassemble the TCP stream from the capture
bash
# List TCP streams found in the capture
tshark -r stream.pcap -q -z conv,tcp

# Follow stream 0 and print it as ASCII
tshark -r stream.pcap -q -z follow,tcp,ascii,0
Expected output
The full reconstructed conversation, including USER admin, PASS S3cr3tPass!, and 230 Login successful — all sent in plaintext over individual TCP segments, now readable as one continuous exchange.
Checkpoint

Detect DNS Beaconing Patterns

Network LAB 4.3
Time: 20 min Tools: bash, tcpdump, tshark, awk
Generate a synthetic "beaconing" pattern — repeated DNS queries at a fixed interval — capture it, then write a short analysis that flags the regular interval as suspicious.
1
Start a capture for 35 seconds
terminal 1
mkdir -p ~/dfir/lab4.3 && cd ~/dfir/lab4.3
sudo timeout 35 tcpdump -i any -w beacon.pcap port 53
2
Generate a fixed-interval DNS query loop

This simulates malware "phoning home" to a C2 domain every 10 seconds.

terminal 2
for i in 1 2 3; do
  nslookup beacon-c2.dfir-lab.example
  sleep 10
done
3
Extract the timestamps of each query to that domain
bash
tshark -r beacon.pcap -Y "dns.qry.name contains \"beacon-c2\"" \
  -T fields -e frame.time_relative -e dns.qry.name
Expected output
Three lines (one per query, possibly more if retries occurred) with relative timestamps roughly 0.0, 10.0, and 20.0 — a near-perfect 10-second interval.
4
Calculate the intervals programmatically
bash
tshark -r beacon.pcap -Y "dns.qry.name contains \"beacon-c2\"" \
  -T fields -e frame.time_relative | awk 'NR>1{print $1-prev} {prev=$1}'
Expected output
A series of values close to 10.0 — the inter-query interval. In a real investigation, intervals this consistent (low variance) across dozens or hundreds of queries to an unfamiliar domain are a strong beaconing indicator.
Checkpoint

Analyse Email Headers for Spoofing

Network LAB 4.4
Time: 15 min Tools: bash, text editor
Examine a provided raw email header sample, trace the Received chain, and determine whether SPF/DKIM results indicate spoofing.
1
Save a sample header to a file

This sample represents a phishing email claiming to be from a finance department.

bash
mkdir -p ~/dfir/lab4.4 && cd ~/dfir/lab4.4
cat > sample_header.txt << 'EOF'
Received: from mx.victimcorp.com (mx.victimcorp.com [198.51.100.10])
        by mailgw.victimcorp.com with ESMTP id a1b2c3
        for <employee@victimcorp.com>; Tue, 14 Mar 2026 09:14:05 +0000
Received: from smtp-relay.attacker-host.net (203.0.113.77)
        by mx.victimcorp.com with ESMTP id d4e5f6
        for <employee@victimcorp.com>; Tue, 14 Mar 2026 09:14:02 +0000
Authentication-Results: mx.victimcorp.com;
    spf=fail smtp.mailfrom=ceo@victimcorp.com;
    dkim=none (no signature found)
From: "CEO - Victim Corp" <ceo@victimcorp.com>
Reply-To: finance.urgent@attacker-host.net
Subject: URGENT: Wire transfer needed before EOD
EOF
cat sample_header.txt
2
Trace the Received chain bottom-to-top
bash
grep "^Received:" sample_header.txt | tac
Expected output
Reading bottom-to-top (the order mail actually travelled), the message originated from smtp-relay.attacker-host.net (203.0.113.77) — not from any victimcorp.com infrastructure — before being relayed to mx.victimcorp.com.
3
Check the authentication results
bash
grep -A2 "Authentication-Results" sample_header.txt
Expected output
spf=fail for smtp.mailfrom=ceo@victimcorp.com, and dkim=none — both checks failed, meaning the receiving server could not verify this message actually came from victimcorp.com's authorised mail servers.
4
Compare the From and Reply-To domains
bash
grep -E "^(From|Reply-To):" sample_header.txt
Expected output
From: ceo@victimcorp.com but Reply-To: finance.urgent@attacker-host.net — any reply to this "urgent" request would go straight to the attacker's domain, not to the real CEO.
5
Write a one-paragraph verdict

Based on steps 2–4, write a short summary (3–4 sentences) stating whether this email is spoofed and what evidence supports that conclusion. Save it as verdict.txt.

bash
nano verdict.txt
Checkpoint

Generate and Correlate Flow-Level Connection Logs

Network LAB 4.5
Time: 25 min Tools: tshark, awk, Zeek (optional)
Convert a packet capture into flow-style connection records (the kind NetFlow or Zeek's conn.log would produce), then correlate those flow records against firewall-style log lines to spot an anomalous high-volume connection.
1
Generate mixed traffic: some normal, one high-volume connection
terminal 1
mkdir -p ~/dfir/lab4.5 && cd ~/dfir/lab4.5
sudo timeout 25 tcpdump -i any -w flows.pcap
terminal 2
# A few small, normal-looking requests
curl -s http://example.com > /dev/null
curl -s http://example.org > /dev/null

# One "high-volume" transfer -- simulate exfiltration with a larger download
curl -s https://speed.hetzner.de/100MB.bin -o /tmp/bigfile --max-time 15 || true
2
Convert the capture into flow-style records with tshark

Each line below approximates one NetFlow/conn.log record: source, destination, port, protocol, and total bytes per conversation.

bash
tshark -r flows.pcap -q -z conv,ip > flow_summary.txt
cat flow_summary.txt
Expected output
A table of IP-to-IP conversations with columns for packets and bytes in each direction. One row — corresponding to the 100MB download — should show a Bytes total dramatically larger than the others (likely megabytes vs. a few KB).
3
Build a simplified conn.log-style CSV
bash
echo "src_ip,dst_ip,dst_port,proto,bytes" > conn.csv
tshark -r flows.pcap -q -z conv,ip | grep -E "^[0-9]" | \
  awk '{gsub(/<-+>/,",",$0); print $1","$3","$NF}' >> conn.csv 2>/dev/null

# Simpler, reliable alternative: per-packet byte totals grouped by destination
tshark -r flows.pcap -T fields -e ip.dst -e frame.len | \
  awk '{sum[$1]+=$2} END {for (ip in sum) print ip","sum[ip]}' | sort -t, -k2 -rn > dst_bytes.csv

cat dst_bytes.csv
Expected output
dst_bytes.csv lists destination IPs sorted by total bytes received, descending. The IP associated with the 100MB download should be at the top, with a byte total an order of magnitude larger than the example.com/example.org entries.
4
Create a synthetic firewall log and correlate by IP

Real investigations correlate flow data against firewall/proxy logs by matching IPs and timestamps. Simulate that correlation here.

bash
# Grab the top destination IP from your flow analysis
TOP_IP=$(head -1 dst_bytes.csv | cut -d, -f1)
echo "Top talker by bytes: $TOP_IP"

# Synthetic firewall log -- one line per allowed connection
cat > firewall.log << EOF
2026-03-14 09:10:01 ALLOW 10.0.0.15 -> 93.184.216.34:80 proto=TCP
2026-03-14 09:10:03 ALLOW 10.0.0.15 -> 96.7.128.175:80 proto=TCP
2026-03-14 09:10:05 ALLOW 10.0.0.15 -> $TOP_IP:443 proto=TCP
EOF

# Correlate: does the top flow-bytes IP appear in the firewall log?
grep "$TOP_IP" firewall.log
Expected output
The firewall log line for the high-volume IP is printed — in a real SOC workflow, this is the moment an analyst pivots from "this connection moved a lot of data" (flow data) to "this connection was explicitly allowed at 09:10:05" (firewall log), narrowing the investigation to a specific rule or time window.
Checkpoint

TLS Certificate Analysis

Network LAB 4.6
Time: 20 min Tools: openssl, tshark
Generate a self-signed certificate (the kind malware C2 infrastructure commonly uses), inspect its fields with OpenSSL, then capture and examine a real TLS handshake to compare a legitimate certificate against the red flags identified in the self-signed one.
1
Generate a self-signed certificate, as a C2 server might
bash
mkdir -p ~/dfir/lab4.6 && cd ~/dfir/lab4.6

openssl req -x509 -newkey rsa:2048 -keyout c2.key -out c2.crt \
  -days 3 -nodes \
  -subj "/CN=update-service.dfir-lab.example"
2
Inspect the certificate's fields
bash
openssl x509 -in c2.crt -noout -text | head -30
Expected output
Note three things in the output: Issuer and Subject are identical (self-signed — no trusted CA vouches for it), the Validity period is only 3 days (malware certs are often short-lived to evade blocklists), and there's no Subject Alternative Name matching a well-known organisation.
3
Start a TLS server using this certificate and capture the handshake
terminal 1
cd ~/dfir/lab4.6
sudo timeout 20 tcpdump -i lo -w tls_handshake.pcap port 8443 &

openssl s_server -accept 8443 -cert c2.crt -key c2.key -www &
sleep 1
terminal 2
curl -sk https://127.0.0.1:8443/ > /dev/null
sleep 3
# Stop the server
pkill -f "s_server"
4
Extract certificate details directly from the captured handshake
bash
tshark -r tls_handshake.pcap -Y "tls.handshake.type == 11" \
  -T fields -e x509ce.dNSName -e x509sat.uTF8String -e tls.handshake.certificate
Expected output
Fields extracted from the Certificate message in the handshake, including the CN value update-service.dfir-lab.example — demonstrating that certificate metadata is visible in plaintext during the handshake even though the application data that follows is encrypted.
5
Compare against a real-world certificate
bash
# Fetch and inspect a real site's certificate for comparison
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
  openssl x509 -noout -issuer -subject -dates
Expected output
FieldSelf-signed C2 certReal-world cert
IssuerSame as Subject (self-signed)A recognised CA (e.g. DigiCert, Let's Encrypt)
Validity3 daysTypically 60–398 days
SubjectArbitrary, attacker-chosen CNMatches the legitimate domain
Checkpoint