# Chapter 2 Labs: Networking, OS Internals, and the Protocol Stack

This pack covers the five hands-on labs in Chapter 2. The chapter builds the
substrate that every later chapter depends on: how packets move, how operating
systems manage processes and permissions, and how containers isolate (or fail
to isolate) workloads. Two of the five labs are fully containerised; three
require host tools because they involve GUI capture software, hardware-level
Wi-Fi monitor mode, or a Windows environment. Each section below is
self-contained and says exactly what you need and where to run it.

---

## Prerequisites

### All labs

- Docker Engine 27+ and Docker Compose v2.27+ (`docker compose version`)
- At least 2 GB free disk (images + build artefacts)

### Lab 2.1 only

- Wireshark 4.x installed on the host (wireshark.org or `sudo apt install wireshark`)

### Lab 2.2 only

- A physical Wi-Fi adapter that supports monitor mode (internal cards rarely do;
  a USB adapter such as Alfa AWUS036ACH works reliably)
- Kali Linux (bare-metal or VM with USB passthrough)
- Written permission for any network you test beyond your own

### Lab 2.5 only

- A Windows 10/11 host or Windows Server VM
- PowerShell 5.1+ (built in) or PowerShell 7+
- Optional: Sysmon installed for the final drill
  (https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon)

---

## Quick Start (containerised labs: 2.3 and 2.4)

```bash
cd /path/to/labs/ch02
docker compose build
docker compose up -d
docker compose ps
```

The `tools` service is in the `tools` profile and is not started automatically.
Run it on demand with:

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

Tear down when finished:

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

---

## Lab 2.1: Wireshark Crash Course

**Host setup (not containerised)**

Wireshark is a GUI desktop application that requires direct access to a host
network interface. It cannot meaningfully run inside a container.

### Objective

Capture live traffic and read DNS, TCP three-way handshake, and TLS ClientHello
packets. Build familiarity with display filters before encountering any
offensive material.

### Setup

1. Install Wireshark from https://www.wireshark.org/download.html
   (current stable: 4.6.x; check the site for the latest point release).
   - Linux: `sudo apt install wireshark` and add your user to the `wireshark`
     group (`sudo usermod -aG wireshark $USER`), then log out and back in.
   - macOS: download the `.dmg` and install the ChmodBPF helper when prompted.
   - Windows: the installer includes Npcap automatically.

### Walkthrough

1. Launch Wireshark. Double-click your active network interface (Wi-Fi or
   Ethernet) to start a live capture.

2. In the display filter bar type `dns` and press Enter. Open any website in a
   browser. Locate the DNS A (or AAAA) query and the corresponding response.
   Note:
   - Transaction ID (matches query to response)
   - TTL on the answer record
   - Resolved IP address

3. Change the filter to `tcp.port == 443`. Watch the three-way handshake:
   - SYN (Flags: 0x002)
   - SYN-ACK (Flags: 0x012)
   - ACK (Flags: 0x010)
   Record the initial sequence numbers on each side.

4. Change the filter to `tls`. Find a ClientHello packet. Expand:
   `Transport Layer Security > TLSv1.3 Record Layer > Handshake Protocol:
   Client Hello > Extension: server_name`
   The SNI field shows the destination hostname in plaintext, sent before
   encryption begins.

5. Save the capture: File > Save As > `lab2-1.pcap`. You will reuse this file
   in the `02_packet_parsing.ipynb` notebook companion.

### Expected Output

- A `lab2-1.pcap` file containing at least one DNS exchange, one TCP handshake,
  and one TLS ClientHello with a visible SNI extension.

### Verify Completion

Open the saved pcap and apply the filter `tls.handshake.type == 1`. At least
one packet must appear with a non-empty `server_name` extension.

### Cleanup

Close Wireshark. No network changes were made.

---

## Lab 2.2: Wi-Fi Security Audit (Passive)

**Host setup (not containerised)**

Monitor-mode Wi-Fi and raw frame injection/capture require a physical Wi-Fi
adapter passed through to the OS. This cannot be virtualised inside a Docker
container.

### Objective

Enumerate visible Wi-Fi networks, classify their security configuration
(WEP/WPA/WPA2/WPA3), capture a four-way handshake from a network you own, and
verify that a strong passphrase defeats offline dictionary attack.

### Safety and Authorisation

Run this lab ONLY against:
- Networks you own (your home router)
- Networks for which you have explicit written permission from the owner

Passive scanning (steps 1-3) is legal in most jurisdictions. Capturing a
handshake (step 4) against a network you do not own is not.

### Prerequisites

- Kali Linux (bare-metal or VM with USB Wi-Fi adapter in passthrough)
- A USB Wi-Fi adapter that supports monitor mode (chipsets: rtl8812au, rtl8814au,
  mt7612u are reliable on Kali)
- Aircrack-ng suite (pre-installed on Kali):
  https://www.aircrack-ng.org/

### Walkthrough

1. Enable monitor mode:
   ```
   sudo airmon-ng check kill
   sudo airmon-ng start wlan0
   ```
   The interface is renamed `wlan0mon` (or `wlan0` with `type monitor`).

2. Enumerate visible networks:
   ```
   sudo airodump-ng wlan0mon
   ```
   For each network record: BSSID, SSID, Channel, ENC, CIPHER, AUTH, signal
   strength (PWR). Note any networks still using WEP or TKIP-only WPA.

3. Look for the WPS column in airodump-ng output. Networks showing WPS enabled
   are vulnerable to Pixie-Dust and online brute-force attacks regardless of
   WPA2 passphrase strength.

4. (Your own network only) Capture the four-way handshake:
   ```
   sudo airodump-ng -c <channel> --bssid <BSSID> -w capture wlan0mon
   ```
   Wait for a client to connect, or reconnect your own device to that network.
   When `WPA handshake: <BSSID>` appears in the header, the capture succeeded.

5. Stop the capture (Ctrl+C). Test the passphrase offline:
   ```
   aircrack-ng -w /usr/share/wordlists/rockyou.txt capture-01.cap
   ```
   A randomly generated passphrase of 20+ characters will not appear in the
   wordlist. A common dictionary word or short phrase will. Observe the
   difference.

6. Restore managed mode:
   ```
   sudo airmon-ng stop wlan0mon
   sudo service NetworkManager restart
   ```

### Expected Output

- `capture-01.cap` containing a WPA2 four-way handshake (airodump-ng header
  shows "WPA handshake")
- Aircrack-ng reports either "KEY FOUND" (weak passphrase) or
  "Passphrase not in dictionary" (strong passphrase)

### Verify Completion

Run `aircrack-ng capture-01.cap` without a wordlist. It should report the ESSID
and confirm an EAPOL four-way handshake is present (message: "1 handshake").

### Cleanup

`sudo airmon-ng stop wlan0mon` and restart NetworkManager.

---

## Lab 2.3: Network in a Box - Docker Compose and Packet Tracing

**Containerised (docker-compose.yml: frontend, backend, database, tools)**

### Objective

Build and inspect a three-tier Docker topology to understand network isolation,
inter-container routing, and packet capture without affecting the host network.
Observe which tiers can reach each other and which cannot.

### Setup

```bash
docker compose build
docker compose up -d
docker compose ps
```

Three containers should be `running`:
- `ch02-frontend` (nginx on port 8080)
- `ch02-backend` (Flask on port 5000, not exposed to host)
- `ch02-database` (PostgreSQL, not exposed to host)

### Walkthrough

**Step 1: Verify the application works end to end.**

```bash
curl http://localhost:8080/
curl http://localhost:8080/db
```

The first request hits nginx, which proxies to the Flask backend. The `/db`
endpoint queries PostgreSQL and returns the server version string.

**Step 2: Test network isolation.**

```bash
# Frontend can reach backend (same frontend-net)
docker exec ch02-frontend ping -c3 backend

# Frontend CANNOT reach database (different network, no route)
docker exec ch02-frontend ping -c3 database
```

The second ping should time out or return "Name or service not known", showing
Docker network isolation in action.

**Step 3: Capture traffic on the frontend interface.**

In terminal 1, start the tools container and capture packets:

```bash
docker compose run --rm tools bash
# Inside the tools container:
tcpdump -i any -n -c 30 port 80 or port 5000
```

In terminal 2, send traffic:
```bash
curl http://localhost:8080/
```

Return to terminal 1 and observe the packets logged by tcpdump.

**Step 4: Discover hosts inside a Docker network.**

Get the frontend container ID:
```bash
FE=$(docker inspect ch02-frontend --format '{{.Id}}')
```

From inside the backend container, scan the backend-net subnet:
```bash
docker exec ch02-backend nmap -sn 172.29.0.0/24
```

Repeat from the frontend container - it is on `172.28.0.0/24` (frontend-net)
and should not reach the database subnet.

**Step 5: Inspect and modify container capabilities.**

```bash
docker inspect ch02-frontend | \
  python3 -c "import sys,json; c=json.load(sys.stdin)[0]['HostConfig']; print('CapAdd:',c['CapAdd'],'CapDrop:',c['CapDrop'])"
```

Edit `docker-compose.yml` and add `cap_drop: [ALL]` under the `frontend`
service. Restart:
```bash
docker compose up -d --force-recreate frontend
curl http://localhost:8080/
```

Note whether nginx still serves traffic. (It should - nginx does not need
elevated capabilities.) Try adding a capability that would break it (e.g.
removing `NET_BIND_SERVICE` on a port < 1024 bind - port 80 inside container)
and observe the error.

### Expected Output

- `curl http://localhost:8080/` returns a JSON response from the Flask backend.
- `ping database` from the frontend times out.
- tcpdump captures HTTP/TCP packets between frontend and backend.

### Verify Completion

```bash
curl -s http://localhost:8080/db | python3 -m json.tool | grep '"db"'
```

Output must include `"db": "reachable"`.

### Cleanup

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

---

## Lab 2.4: Linux for Security - Twenty Essential Commands

**Containerised (docker-compose.yml: linux-sandbox)**

The `ch02-linux-sandbox` container runs Ubuntu 24.04 seeded with a synthetic
`/var/log/auth.log`, a world-readable script in `/home/labuser/`, a SUID binary
at `/usr/local/bin/lab-ping`, and a cron entry - giving realistic output for
every command in the lab without touching the host system.

### Objective

Build fluency with the eight command categories that security analysts use daily
on Linux: process inspection, file/permission audit, log parsing, user context,
capabilities, namespaces, scheduled jobs, and journalctl.

### Setup

```bash
docker compose up -d linux-sandbox
docker exec -it ch02-linux-sandbox bash
```

All commands below run inside the container unless noted.

### Walkthrough

**1. Process inspection**

```bash
ps aux | grep ssh
lsof -i :22
ss -tulpn
```

Note: inside the container, SSH is installed but not running, so `lsof -i :22`
will return nothing - which is itself a useful observation. Run
`ss -tulpn | grep LISTEN` to see what is actually listening.

**2. File and permission audit**

```bash
# World-readable shell scripts in home directories
find /home -perm -004 -name "*.sh" 2>/dev/null

# SUID root binaries
find / -perm -4000 -user root -type f 2>/dev/null
```

Expected: `/home/labuser/deploy.sh` in the first find, `/usr/local/bin/lab-ping`
(and others like `/usr/bin/passwd`) in the second.

**3. Auth log parsing**

```bash
grep "Failed password" /var/log/auth.log \
  | awk '{print $11}' \
  | sort | uniq -c | sort -rn | head -20
```

Expected: `198.51.100.10` appears most frequently (it is seeded with the most
failed attempts). These are RFC 5737 documentation addresses - not real attacker
IPs.

**4. User and group context**

```bash
id
getent passwd | awk -F: '{if ($3 < 1000 && $7 != "/sbin/nologin" && $7 != "/bin/false") print}'
```

Note the system accounts that have a real shell (e.g. `root`).

**5. Capabilities**

```bash
cat /proc/self/status | grep Cap
capsh --decode=$(cat /proc/self/status | grep CapEff | awk '{print $2}')
```

Compare the capability bitmask when you run as `root` vs `su - labuser`.

**6. Namespace inspection**

```bash
ls -la /proc/self/ns/
ip netns list
```

Inside the container you are in a dedicated network namespace. `ip netns list`
will be empty because named namespaces are not created by Docker by default.

**7. Scheduled jobs**

```bash
crontab -l
ls -la /etc/cron.*
cat /etc/crontab
```

The seeded crontab entry at `/etc/crontab` shows a root job running `lab-ping`
nightly at 03:00 - a plausible persistence indicator to spot.

**8. journalctl drills**

Systemd is not PID 1 inside this container (it uses `sleep infinity`), so
`journalctl` will report no journal. This is intentional and worth noting:

```bash
journalctl -k --since "1 hour ago" 2>&1 || echo "No systemd journal (container)"
```

On a real Linux host the command returns kernel messages. The absence here
illustrates a container limitation relevant to forensics.

### Expected Output

- `find /home -perm -004 -name "*.sh"` returns `/home/labuser/deploy.sh`
- `find / -perm -4000 -user root -type f` returns `/usr/local/bin/lab-ping` (at minimum)
- Auth log grep returns `198.51.100.10` as top offender with 8 failed attempts

### Verify Completion

```bash
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -1
```

Output must show `198.51.100.10` with count >= 5.

### Cleanup

```bash
exit   # leave the container shell
docker compose stop linux-sandbox
```

---

## Lab 2.5: Windows for Security - PowerShell Drills

**Host setup (not containerised)**

PowerShell drills require a genuine Windows kernel for WMI, registry hives,
Windows event log APIs (`Get-WinEvent`), and the Windows security token model.
Wine and WSL do not expose these APIs, so container emulation is not viable.

### Objective

Navigate a Windows system as a security analyst: enumerate processes, services,
scheduled tasks, event log entries, registry persistence keys, ACLs, and local
users - all using built-in PowerShell cmdlets.

### Prerequisites

- Windows 10 21H2+, Windows 11, or Windows Server 2019/2022 (VM is fine)
- PowerShell 5.1 (built in) or PowerShell 7+ (https://aka.ms/powershell)
- Optional: Sysmon installed (https://learn.microsoft.com/sysinternals/downloads/sysmon)
  for the final drill

### Walkthrough

Open an **elevated** PowerShell prompt (right-click > Run as Administrator).

**1. Process and service enumeration**

```powershell
# Top 20 processes by CPU
Get-Process | Sort-Object CPU -Descending | Select-Object -First 20

# All running services
Get-Service | Where-Object {$_.Status -eq "Running"}
```

Note the `StartType` column. Services set to `Automatic` that are not Microsoft
signed binaries deserve closer inspection.

**2. Scheduled task audit**

```powershell
Get-ScheduledTask |
  Where-Object {$_.TaskPath -notlike "\Microsoft\*"} |
  Select-Object TaskName, TaskPath, State
```

Any task outside `\Microsoft\` is third-party. Malware persistence often
appears here. Note the `Actions` property for the executable path.

**3. Event log queries**

```powershell
# Last 50 failed logons (Event ID 4625)
Get-WinEvent -LogName Security `
  -FilterXPath "*[System[EventID=4625]]" `
  -MaxEvents 50 |
  Format-List TimeCreated, Message
```

Look at the `SubStatus` field: `0xC0000064` means the username did not exist
(enumeration attempt); `0xC000006A` means wrong password for a valid account.

**4. Registry run key inspection**

```powershell
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
```

Every entry here launches at system boot or user logon. Unexpected entries
(not matching installed software) are a persistence indicator.

**5. ACL inspection**

```powershell
Get-Acl C:\Windows\System32\drivers\etc\hosts | Format-List
icacls C:\ProgramData /T 2>$null | Select-String "Everyone|Users.*(W|F)"
```

The hosts file ACL should show `NT AUTHORITY\SYSTEM` and `BUILTIN\Administrators`
as the only write principals. If `Users` has write access, that is a finding.

**6. Local user and group audit**

```powershell
Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordLastSet
Get-LocalGroupMember Administrators
```

Any enabled local account with no recent logon and a PasswordLastSet date from
years ago is a dormant account worth investigating.

**7. Sysmon process creation events (if Sysmon is installed)**

```powershell
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" `
  -FilterXPath "*[System[EventID=1]]" `
  -MaxEvents 20 |
  Format-List TimeCreated, Message
```

Sysmon Event ID 1 includes full command lines, parent process, and a process
GUID for correlation. Compare with Event ID 4688 (Security log) to see what
information is lost without Sysmon.

### Expected Output

- Scheduled task list shows only Microsoft tasks on a clean system; any
  additions you made for testing appear immediately.
- Registry run keys show installed software entries.
- Failed-logon query returns events if any login failures occurred; on a clean
  VM it may be empty.

### Verify Completion

Run the following and confirm it returns output (not an error):

```powershell
Get-LocalGroupMember Administrators | Select-Object Name, ObjectClass
```

At minimum `Administrator` should appear.

### Cleanup

No cleanup required. The PowerShell commands were read-only queries.

---

## Teardown (containerised labs)

```bash
docker compose -f /path/to/labs/ch02/docker-compose.yml down -v
```

The `-v` flag removes the named `db-data` volume. Omit it if you want to
preserve the PostgreSQL data between sessions.

---

## Safety and Authorisation

All offensive-adjacent techniques in this chapter are strictly passive or
directed at infrastructure you own:

- Lab 2.2 (Wi-Fi): passive scanning is legal in most jurisdictions; handshake
  capture and cracking must only target your own network or one with explicit
  written permission.
- Lab 2.3 (Docker): all targets are local containers. No traffic leaves your
  machine.
- Lab 2.4 (Linux): all targets are a local container with synthetic data.
- Labs 2.1 and 2.5: read-only observation of your own system.

Never run these techniques against networks, systems, or accounts you do not
own or do not have explicit written authorisation to test.
