# Chapter 15 Labs: Forensic Principles and Analysis - Advanced (Part II)

This lab pack covers the six hands-on exercises from Chapter 15 of
*Applied Cybersecurity: Foundations to Mastery*. The chapter covers
sound acquisition, Windows and cross-platform artefact analysis, memory
forensics with Volatility 3, network forensics with Zeek and Suricata,
cloud and container forensics, anti-forensics detection, and
expert-witness-grade reporting.

Four of the six labs run fully inside Docker. Two are host setups:
Lab 15.6b (container forensics) uses the host Docker daemon directly,
and Lab 15.6c (report drill) is a writing exercise with no dedicated service.

## Prerequisites

- Docker Desktop (or Docker Engine + Compose v2) -- minimum 4 GB RAM allocated
- About 3 GB free disk space for image builds
- Python 3.x on the host (only to run `seed/pcap/generate_corpus.py`)
- For Lab 15.4: a practice Windows memory image (see `seed/memory/README.txt`)
- For Lab 15.2: practice Windows Registry hive files (see `seed/hives/README.txt`)

## Quick Start

```bash
# Generate the synthetic PCAP corpus first (one-time, no pip deps needed)
python3 seed/pcap/generate_corpus.py

# Build images and start all services
docker compose up -d

# Interactive shell for Labs 15.2 and 15.4 (RegRipper + Volatility 3)
docker compose run --rm forensic-tools bash

# Interactive shell for Lab 15.5 (Zeek + Suricata)
docker compose run --rm network-tools bash

# Jupyter notebooks for Lab 15.4 (walkthrough) and Lab 15.6a (CloudTrail triage)
# http://localhost:8888  (token: labtoken)
```

---

## Lab 15.2 - Registry Surgery with RegRipper: USB Devices and Executed Binaries

### Objective

Use RegRipper to extract USB device history and executed-binary evidence from
Windows Registry hives, producing sourced evidence statements tied to acquisition hashes.

### Setup

Container: `forensic-tools` (provides RegRipper 3.0 and sha256sum).

Place practice hive files in `evidence/`:
- `SYSTEM` -- from `C:\Windows\System32\config\`
- `SOFTWARE` -- from `C:\Windows\System32\config\`
- `NTUSER.DAT` -- from a user profile root

See `seed/hives/README.txt` for sources of legitimate practice images
(NIST CFReDS, CyberDefenders, your own Windows VM).

```bash
docker compose run --rm forensic-tools bash
```

### Walkthrough

Inside the container at `/labs/evidence`:

**Step 1: Record acquisition hashes before touching anything**
```bash
sha256sum SYSTEM SOFTWARE NTUSER.DAT
# Note each hash in your case notes. This is the acquisition hash.
```

**Step 2: List available plugins**
```bash
rip.pl -l | grep -E 'usbstor|usbdevices|mountdev|userassist'
```

**Step 3: Reconstruct USB device history from the SYSTEM hive**
```bash
rip.pl -r /labs/evidence/SYSTEM -p usbstor
rip.pl -r /labs/evidence/SYSTEM -p mountdev
```
Record the device serial numbers, vendor/product strings, and first/last-connected timestamps.

**Step 4: Reconstruct executed-binary evidence from the user hive**
```bash
rip.pl -r /labs/evidence/NTUSER.DAT -p userassist
```
Note the program name, run count, and last-run time for any program of interest.

**Step 5: Cross-check against Prefetch (if available in evidence)**
```bash
# Prefetch files are .pf files from C:\Windows\Prefetch\ in the imaged disk
# If extracted, use any PF parser or strings to confirm run count and last-run time
# matching the UserAssist entry
file /labs/evidence/*.pf 2>/dev/null
```

**Step 6: Write evidence statements**

Format each finding as:
```
USB device <vendor/serial> first connected <time>,
  per SYSTEM USBSTOR (hive SHA-256: <hash>).
Program <name> run <N> times, last <time>,
  per NTUSER.DAT UserAssist (hive SHA-256: <hash>),
  corroborated by Prefetch entry <name.pf>.
```

Note every timestamp as a key-last-write or artefact-recorded time,
not an unqualified action time.

### Expected Output

RegRipper outputs structured text per plugin -- for `usbstor` you see device
records with VID/PID/serial and timestamps; for `userassist` you see a table
of GUI-launched programs with run counts and UTC last-run times.

### Verify Completion

- [ ] Each hive hash recorded before analysis
- [ ] USB device serial number, vendor, first/last-connected time identified
- [ ] At least one program from UserAssist with run count and last-run time
- [ ] If Prefetch available: UserAssist entry corroborated by matching .pf entry
- [ ] All findings written as evidence statements with source hive and hash cited

### Cleanup

```bash
# No persistent data outside evidence/ - just exit the container
exit
```

---

## Lab 15.4 - Volatility 3 on a Provided Memory Dump

### Objective

Use Volatility 3 to enumerate processes, detect injected code, and surface
ransomware indicators in a Windows memory image.

### Setup

Container: `forensic-tools` (provides Volatility 3).

Place your practice memory image at `evidence/mem.raw`.
See `seed/memory/README.txt` for sources of legitimate practice images.

The Jupyter companion notebook `notebooks/15a_volatility_walkthrough.ipynb`
walks the same sequence interactively (http://localhost:8888, token: labtoken).

```bash
docker compose run --rm forensic-tools bash
```

### Walkthrough

Inside the container, all commands use `vol.py` as the Volatility 3 entry point.

**Step 1: Hash the image**
```bash
sha256sum /labs/evidence/mem.raw
# Record this acquisition hash. Re-hash at the end; they must match.
```

**Step 2: Baseline the processes**
```bash
vol.py -f /labs/evidence/mem.raw windows.pslist
vol.py -f /labs/evidence/mem.raw windows.pstree
```
Read the tree for parent-child anomalies:
- cmd.exe or powershell.exe under an Office application (malicious macro)
- lsass.exe with a parent that is not wininit.exe
- System-named process running from a user directory
- Multiple instances of a normally-singleton process

Record any suspicious PID.

**Step 3: Hunt hidden processes**
```bash
vol.py -f /labs/evidence/mem.raw windows.psscan
# Compare against pslist: any PID in psscan but not pslist is a hidden/terminated candidate
```

**Step 4: Detect injected code**
```bash
vol.py -f /labs/evidence/mem.raw windows.malfind
```
For each hit, note:
- PAGE_EXECUTE_READWRITE (RWX region): writable AND executable -- injected code signature
- Private/unbacked VAD: executable memory with no backing file on disk
- MZ (4D 5A) PE header in private memory: reflectively loaded module

All three together = high-confidence injected code finding.

**Step 5: Interrogate the suspect process**
```bash
# Replace 4242 with the suspicious PID from Steps 2-4
vol.py -f /labs/evidence/mem.raw windows.cmdline --pid 4242
vol.py -f /labs/evidence/mem.raw windows.dlllist --pid 4242
vol.py -f /labs/evidence/mem.raw windows.netscan
```

**Step 6: Find ransomware indicators**
```bash
# Shadow-copy deletion
vol.py -f /labs/evidence/mem.raw windows.cmdline | grep -i vssadmin

# Mass file-handle activity
vol.py -f /labs/evidence/mem.raw windows.handles --pid 4242

# Dump open files (ransom note, staged data)
vol.py -f /labs/evidence/mem.raw windows.dumpfiles --physaddr <addr>
```

### Expected Output

- `pslist`/`pstree`: a process list and tree; at least one anomalous lineage if using a known malware sample
- `psscan` diff: any hidden/terminated processes not in pslist
- `malfind`: flagged RWX/private/PE regions with hex dumps showing MZ header
- `cmdline`: full launch commands including any encoded PowerShell or vssadmin calls
- `netscan`: open sockets and connections; since-closed C2 may still appear

### Verify Completion

- [ ] Acquisition hash recorded; verification hash matches at end
- [ ] At least one parent-child anomaly or hidden process identified (or confirmed absent)
- [ ] malfind output reviewed; RWX/private/MZ pattern described
- [ ] cmdline checked for vssadmin shadow deletion
- [ ] Short timeline written: injected process -> launch command -> C2 connection -> ransomware tell

### Cleanup

```bash
exit
```

---

## Lab 15.5 - Triage a Defanged PCAP Corpus with Zeek and Suricata

### Objective

Run Zeek and Suricata over a synthetic/defanged capture corpus, surface a
beaconing pattern, pivot on uid across Zeek logs, hash transferred files,
and reconcile Suricata alerts against Zeek's structured substrate.

### Setup

Container: `network-tools` (provides Zeek 8.x and Suricata 7.x/8.x).
The synthetic corpus is pre-loaded at `/labs/pcap/corpus.pcap`.
Custom Suricata rules are at `/labs/rules/suricata.rules`.

First, generate the corpus on the host (one-time):
```bash
python3 seed/pcap/generate_corpus.py
```

Then start the container:
```bash
docker compose run --rm network-tools bash
```

**Safety:** The corpus is fully synthetic (all IPs are RFC 5737 documentation
addresses; all domains are .invalid or .example). Do not replay any captured
C2 traffic onto a live network. Never resolve or connect to indicators extracted
from live incident captures outside the lab network.

### Walkthrough

Inside the container at `/labs`:

**Step 1: Generate structured logs with Zeek**
```bash
mkdir -p /labs/zeek-out && cd /labs/zeek-out
zeek -C -r /labs/pcap/corpus.pcap
ls -la
# Confirm: conn.log, dns.log, http.log, ssl.log, files.log, notice.log
```

**Step 2: Find the beacon in conn.log**
```bash
zeek-cut id.orig_h id.resp_h id.resp_p duration orig_bytes < /labs/zeek-out/conn.log \
  | sort | uniq -c | sort -rn | head -20
```
The destination that repeats most often at a near-constant byte size and
near-constant interval is the beaconing signature.
Note the `uid` values of those rows.

**Step 3: Pivot on uid into dns.log and ssl.log**
```bash
# Replace <uid> with a UID from Step 2
grep <uid> /labs/zeek-out/dns.log
grep <uid> /labs/zeek-out/ssl.log
```
Recover the C2 domain (DNS query name) and, for encrypted flows, the TLS SNI.
The destination survives encryption even when http.log is empty.

**Step 4: Carve and hash transferred files**
```bash
zeek-cut mime_type sha256 md5 < /labs/zeek-out/files.log
# Record hashes as indicators. Do NOT execute any extracted file.
```

**Step 5: Run Suricata**
```bash
mkdir -p /labs/out
suricata -r /labs/pcap/corpus.pcap -S /labs/rules/suricata.rules -l /labs/out
jq -c 'select(.event_type=="alert") | {sig:.alert.signature, sid:.alert.signature_id, src:.src_ip, dst:.dest_ip}' \
   /labs/out/eve.json
```

**Step 6: Reconcile Zeek and Suricata**

For each Suricata alert SID, find the matching connection in Zeek:
- Does `conn.log`/`ssl.log` show the same src/dst at the same time?
- Does the alert match what Zeek independently observed?
- Read the rule behind the SID before trusting its `msg` label.

**Step 7: Write three findings**
```
1. Beaconing host: 192.0.2.10 -> C2 lab-c2.example.invalid:80
   Evidence: conn.log (10 connections, ~60s interval, ~420 bytes each)
   + dns.log (10 queries for lab-c2.example.invalid)

2. Corroborated Suricata alert: SID 9150001 "EvilBot/1.0 User-Agent"
   Confirmed by: http.log shows http.user_agent = EvilBot/1.0 for same src/dst

3. Transferred file: files.log SHA-256 <hash>, mime=application/octet-stream
   (EICAR test string - benign synthetic indicator)
```

### Expected Output

- Zeek writes 6+ structured log files from the 3 KB corpus
- Zeek conn.log shows 10+ repeated connections to 198.51.100.42
- dns.log shows 10 queries for lab-c2.example.invalid
- Suricata fires at least 2-3 alerts (SIDs 9150001-9150004) from the teaching rules
- Files.log contains one entry with EICAR-string hash

### Verify Completion

- [ ] Zeek generated conn.log, dns.log, http.log, files.log
- [ ] Beaconing destination identified (repeated connections at fixed interval)
- [ ] C2 domain recovered from dns.log via uid pivot
- [ ] File hash extracted from files.log
- [ ] At least one Suricata alert confirmed against Zeek logs (not just trusted on msg)
- [ ] Three written findings each tracing to a specific log row

### Cleanup

```bash
exit
docker compose down -v
```

---

## Lab 15.6a - Cloud Forensics on Stub CloudTrail + S3 Access Logs

### Objective

Triage a stub CloudTrail event file and S3 server access log to reconstruct
a key-compromise attack arc: reconnaissance, IAM persistence, logging blind,
and S3 exfiltration.

### Setup

Container: `jupyter` (Python 3 + pandas + notebook).
Access: http://localhost:8888 (token: labtoken)

Open the notebook: `15b_cloudtrail_triage.ipynb`

Stub files are pre-loaded (read-only) in the container at:
- `/labs/seed/cloudtrail/cloudtrail.json`
- `/labs/seed/cloudtrail/s3-access.log`

No live AWS account and no credentials are required.

### Walkthrough

Follow the cells in `15b_cloudtrail_triage.ipynb`:

1. Load and flatten the CloudTrail `Records[]` array to a pandas DataFrame
   keyed on `eventTime`, `eventName`, `eventSource`, `userIdentity.arn`,
   `userIdentity.accessKeyId`, `sourceIPAddress`, and `userAgent`.

2. Find the foothold: filter `eventName` in `{CreateUser, CreateAccessKey,
   AttachUserPolicy, AttachRolePolicy}`. Note the new principal and the
   policy attached (AdministratorAccess is the tell).

3. Find the blinding attempt: filter `eventName == 'StopLogging'`.
   Record its `eventTime`; events after it may be unlogged.

4. Find the exfiltration: count `GetObject` per `sourceIPAddress` / `accessKeyId`.
   The outlier with many reads in minutes is the exfil session.
   Cross-check against `s3-access.log` to total bytes returned.

5. Integrity validation: record the procedure you would run
   (`aws cloudtrail validate-logs`) to prove the log was not tampered.
   Write this into your evidence notes.

6. Deliver a one-paragraph timeline anchored to `eventTime` and `userIdentity`.

### Expected Output

- Timeline spanning 08:14:22Z (reconnaissance) through 08:25:13Z (exfiltration)
- IAM persistence: user `backup-svc` with `AdministratorAccess` created
- StopLogging at 08:22:04Z by `devops-ci` key
- 3x GetObject from `prod-customer-data-111122223333` by `backup-svc` from 203.0.113.77
- Approximate 6.4 MB exfiltrated (sum of bytes from S3 access log)

### Verify Completion

- [ ] DataFrame built from cloudtrail.json
- [ ] Foothold principal and policy identified
- [ ] StopLogging event time recorded; its implication for timeline bounds stated
- [ ] GetObject burst identified by source IP and accessKeyId
- [ ] S3 access log cross-checked for byte total
- [ ] Integrity validation step documented
- [ ] One-paragraph timeline written with each event anchored to eventTime and userIdentity

### Cleanup

Close the Jupyter tab. Run `docker compose down -v` when finished with all labs.

---

## Lab 15.6b - Container Forensics: Compromised Container, then Image Lineage

### Host Setup (not containerised here)

This lab uses the host Docker daemon directly to demonstrate
`docker diff`, `docker commit`, and `docker history` on a
"compromised" lab container. Running this inside another container
would require privileged Docker-in-Docker, which is avoided for safety.

**Upstream reference:** Docker Engine documentation --
https://docs.docker.com/engine/reference/commandline/diff/
https://docs.docker.com/engine/reference/commandline/commit/
https://docs.docker.com/engine/reference/commandline/history/

**Authorisation guard rail:** run only against the provided lab container or one you own.

#### Setup

Build the synthetic lab target (simulates a compromised nginx with a benign "backdoor layer"):
```bash
docker build -t ch15-lab-target:case01 seed/container-image/
docker run -d --name ch15-lab-target ch15-lab-target:case01
```

Or with make:
```bash
make lab-container
docker run -d --name ch15-lab-target ch15-lab-target:case01
```

#### Walkthrough

**Step 1: Triage the running container without disturbing it**
```bash
docker diff ch15-lab-target
```
Note every A (added), C (changed), D (deleted) path.
Look for additions under `/tmp`, `/dev/shm`, or the web root.

**Step 2: Freeze the evidence**
```bash
docker commit ch15-lab-target incident-evidence:case01
docker save incident-evidence:case01 -o incident-evidence-case01.tar
sha256sum incident-evidence-case01.tar
```
The tar is your forensic export for offline analysis.
Record the SHA-256 -- this is the image acquisition hash.

**Step 3: Establish image lineage**
```bash
docker history --no-trunc ch15-lab-target:case01
```
Read the build steps. The `RUN echo "# Synthetic backdoor marker..."` line
near the top is the unexpected late instruction on a trusted nginx base --
exactly the pattern a real baked-in backdoor produces.

**Step 4: Hunt the malicious layer by digest comparison**
```bash
# Record the suspect image layer digests
docker inspect ch15-lab-target:case01 | python3 -c \
  "import json,sys; [print(l) for l in json.load(sys.stdin)[0]['RootFS']['Layers']]"

# Compare against the clean nginx base
docker pull nginx:1.27-alpine
docker inspect nginx:1.27-alpine | python3 -c \
  "import json,sys; [print(l) for l in json.load(sys.stdin)[0]['RootFS']['Layers']]"
```
Layers not present in the clean base are attacker-added layers.
The extra digests are your tamper-evident anchors.

**Step 5: Capture process state (if container is still running)**
```bash
# On Linux: find the container's main PID on the host
docker inspect --format '{{.State.Pid}}' ch15-lab-target
# Then use your host memory capture tool (AVML, LiME, or /proc/<pid>/mem)
# This is the most volatile evidence -- capture before any other action.
```

#### Expected Output

- `docker diff` shows additions under `/tmp` (`.lab-backdoor.sh`) and a modified entrypoint
- `docker history` shows 2 unexpected `RUN` layers baked onto the nginx base
- Layer digest comparison reveals 2 extra digests in the suspect image absent from the clean base
- `incident-evidence-case01.tar` exported with SHA-256 recorded

#### Verify Completion

- [ ] `docker diff` output reviewed; unexpected paths noted
- [ ] Evidence image committed and exported; SHA-256 recorded
- [ ] `docker history --no-trunc` read; late/unexpected RUN instructions identified
- [ ] Layer digests compared; extra layers in suspect image identified
- [ ] All digests recorded verbatim in case notes as tamper-evident anchors

#### Cleanup

```bash
docker stop ch15-lab-target
docker rm ch15-lab-target
docker rmi ch15-lab-target:case01 incident-evidence:case01
```

---

## Lab 15.6c - 10-Page Forensic Report Drill from Volatility Findings

### Host Setup (not containerised -- writing exercise)

This is a structured writing drill. No dedicated service is required.
Use the Volatility 3 findings from Lab 15.4 (`15a_volatility_walkthrough.ipynb`)
as the evidence base.

#### Objective

Produce a 10-page expert-witness-grade forensic report structured to meet
FRE 702 / Daubert reliability criteria.

#### Walkthrough

Using your notes from Lab 15.4:

**Section 1 - Executive Summary (half page):**
State in plain English what you found (example: "a process was running injected
code consistent with credential theft; data was staged for exfiltration") with
zero jargon and zero overreach. A non-technical reader must understand the impact.

**Section 2 - Scope and Authorisation (half page):**
What you examined, under whose instruction, and the explicit boundary of what
was out of scope.

**Section 3 - Methodology and Tools (1 page):**
List every tool with its exact version (Volatility 3 v2.x, acquisition tool,
hashing utility), every command run, and the acquisition + verification SHA-256
hashes showing they match.

**Section 4 - Chain of Custody (half page):**
The custody table from seizure to analysis.

**Sections 5 and 6 - Findings and Analysis (4-5 pages):**
For each Volatility finding (malfind injected region, suspicious pstree
parent-child, netscan C2 connection), tie the conclusion to the specific plugin
output that supports it. Consider and address at least one alternative explanation.

**Section 7 - Conclusions (half page):**
Calibrated certainty: separate what the evidence proves from what it suggests.
"Consistent with", "indicates", and "proves" are different words -- use them precisely.

**Section 8 - Appendices:**
Raw plugin output and hash logs.

**Self-test against Daubert/FRE 702:**
For each finding, ask: "Could a second examiner reproduce this, and is the
method one a court would accept?" If the answer is no, the finding is not yet
report-ready.

#### Verify Completion

- [ ] All eight sections present
- [ ] Every tool cited with exact version
- [ ] Acquisition hash and verification hash both recorded; they match
- [ ] Every finding traces to named plugin output (not free-floating assertion)
- [ ] At least one alternative explanation addressed
- [ ] Conclusions use calibrated language (not "the attacker did X" from a userIdentity)
- [ ] Daubert self-test applied to each finding

---

## Teardown

```bash
docker compose down -v
# Remove built images if desired:
docker rmi ch15-forensic-tools ch15-network-tools 2>/dev/null || true
```

---

## Safety and Authorisation

All labs in this chapter use either:
- Synthetic/defanged data (the PCAP corpus, CloudTrail stub, S3 log stub, container image)
- Evidence images from your own systems or authorised public corpora

Never run these tools against systems you do not own or have written authority to analyse.
Never replay captured C2 traffic onto a live network.
Never resolve or attempt to connect to indicators extracted from live incident captures.

The EICAR test string in the PCAP corpus is a standard AV test file (not real malware).
The CloudTrail and S3 stub data contains no real account IDs, credentials, or personal data.
The lab container image contains no real malware -- only benign marker files.
