Skip to content

Repository files navigation

alt text

grepLabs

A general-purpose security research lab you build with Terraform.

grepLabs is an Azure lab for practicing hands-on security work across several disciplines. Depending on what you load into it, it runs as a reverse engineering environment, a burner box for safely visiting hostile infrastructure, a CI/CD supply chain security lab, or a detection engineering testbed. The infrastructure is the same, but what you do with it is up to you.

This is the fuller, RE-focused sibling to my CICDefense repo. CICDefense is the leaner CI/CD supply chain lab with a stock-Ubuntu adversary node. grepLabs adds real Kali, a Windows 11 box, and full reverse engineering toolsets across the runners.

AI Disclaimer

Note: All code, infrastructure, and technical work here is mine. I used limited AI assistance to help draft the README, since capturing every feature and update across a large build is a lot to remember and type by hand.

Lab Architecture

Four VMs on a private network, reachable only through a managed jump host. No VM has a public IP.

  • Ubuntu (latest LTS): a general-purpose Linux workstation. Runs as a CI runner, a detection engineering host, or a reverse engineering box. Carries the Linux RE toolset, language toolchains, and host/network detection tooling.
  • Windows Server 2022 Datacenter: a Windows workstation for the same general-purpose work, with the Windows RE toolset and detection tooling.
  • Windows 11 (24H2 Enterprise): a second Windows workstation, same RE and detection loadout as the Server box, for when you want a desktop-Windows target or a second analysis environment.
  • Kali Linux (marketplace image): the adversary and threat-intelligence node. Provisioned via cloud-init with a full XFCE desktop, Firefox, and Tor Browser preloaded with ransomware leak-site bookmarks. This is the box you use to reach hostile infrastructure from an isolated, destroyable host.

Every box except Kali can serve as a CI runner, a detection engineering host, or a reverse engineering environment. Kali is purpose-built as the adversary/threat-intel node.

Network posture:

  • VMs can reach each other
  • VMs can reach the internet (egress allowed for package and model pulls)
  • Nothing on the internet can reach the VMs (no ingress)
  • Operator access is via Azure Bastion (Note: Standard SKU is an intentional choice. It is required if you want to connect directly to the Linux hosts via RDP in the Azure portal.)

What Gets Built

Azure (roughly 20 resources):

  • Resource group, virtual network, workload subnet, AzureBastionSubnet
  • Network security group with least-privilege rules, plus subnet association
  • Azure Bastion host (Standard SKU) and its static public IP
  • 4 network interfaces, no public IPs
  • 4 virtual machines (Ubuntu, Windows Server 2022, Windows 11, Kali)
  • 3 VM extensions that install desktops and tooling on the Ubuntu and Windows boxes
  • A Kali marketplace agreement (Kali is provisioned via cloud-init, not an extension)

Prerequisites

  • Terraform CLI 1.9 or newer
  • Azure CLI
  • An Azure subscription with billing enabled
  • Git

Optional but recommended

Note: These have to be manually created/configured.

  • VS Code with the HashiCorp Terraform extension
  • Multiple dummy accounts, if you use grepLabs for CI/CD supply chain scenarios
    • GitHub
    • Azure
    • CircleCI

Detection & Tooling Overview

The lab's capabilities are organized in layers.

Composition / SBOM analysis (what is actually in a build)

  • Syft (SBOM generation), Grype (CVE scan), Trivy (vuln + secret scan)

Behavioral package scanning (novel or suspicious dependencies)

  • Socket CLI, npm audit

Host telemetry (process, file, and identity events)

  • Ubuntu: auditd (execve + identity rules), osquery
  • Windows: Sysmon (SwiftOnSecurity config)

Network visibility (what a host talks to)

  • Wireshark / tshark, mitmproxy

Reverse engineering (static and dynamic binary analysis)

  • Linux: Ghidra, rizin, ilspycmd, jadx, jd-gui, Detect It Easy, Nodejs-Tracer
  • Windows: x64dbg, dnSpyEx, de4dot, System Informer, Sysinternals, Ghidra, DIE, jd-gui, Nodejs-Tracer

Build toolchains (the attack surface being defended, and the languages you compile and analyze)

  • Docker, Node/npm, Python, Go, Rust, JDK/Maven, .NET

Adversary and threat intel node (reaching hostile infrastructure safely or simulating attacker behavior yourself)

  • Kali node with Tor Browser and ransomware leak-site bookmarks, isolated and destroyable
  • Kali ships its standard offensive toolset. It is the attack platform and the threat-intel workstation.

Quick Start

1. Authenticate

az login
az account show --query id --output tsv

Copy the subscription GUID that prints.

2. Create your variables file

terraform.tfvars is gitignored and is not in this repo. Create it from the template:

copy terraform.tfvars.example terraform.tfvars

Then fill in:

  • subscription_id: the GUID from step 1
  • location: defaults to centralus
  • admin_username: cannot be admin, administrator, root, guest, user, or test
  • admin_password: 12+ characters, mixed case, number, symbol
  • user_password: 12+ characters, for the non-privileged nottavictim account

To keep a password off disk, set it as an environment variable instead and omit it from the file:

$env:TF_VAR_admin_password = "your-password-here"

3. Deploy

terraform init
terraform validate
terraform plan
terraform apply

Expect 20 to 40 minutes. Bastion takes 5 to 10 minutes on its own, and the Windows and Ubuntu boxes install substantial toolsets. The Kali box provisions via cloud-init and reboots itself when it finishes.

4. Connect

terraform output

In the Azure Portal, go to the resource group, select a VM, then Connect, then Bastion. Sign in with your admin username and password.

All four VMs accept RDP. xrdp is installed on the Linux nodes so they present a graphical desktop rather than a bare shell. For Linux VMs, in the Bastion Connect pane select Protocol: RDP, port 3389 (enabled by the Standard SKU). Native-client tunneling via az network bastion tunnel + mstsc also works.

Connecting via the native RDP client (PowerShell tunnel)

The Bastion Connect pane in the portal works for both Windows (RDP) and Linux (RDP via the Standard SKU protocol option). If you would rather use a local RDP client, for clipboard sharing, file transfer, or multi-monitor, open a Bastion tunnel from PowerShell and connect through it.

Requires the Bastion Standard SKU with tunneling_enabled = true (both are set in bastion.tf).

Open a tunnel to a VM. Leave this window running, it holds the tunnel open:

az network bastion tunnel `
  --name greplabs-bastion `
  --resource-group greplabs-rg `
  --target-resource-id "<VM_RESOURCE_ID>" `
  --resource-port 3389 `
  --port 13389

Get the VM's resource ID with:

az vm show --resource-group greplabs-rg --name greplabs-kali --query id --output tsv

Then, in a second window, launch Remote Desktop against the local tunnel port:

mstsc /v:localhost:13389

Log in with your admin_username and admin_password. For a second VM, open another tunnel on a different local port (e.g. --port 13390) so both can run at once.

5. Teardown

terraform destroy

Do this at the end of every session.

Installed Tooling

Provisioning scripts live in scripts/ and run automatically via VM extensions, except Kali, which provisions via cloud-init.

On the Ubuntu box, heavier toolchains and scanners install in the background after first boot via a systemd one-shot unit, so the deployment finishes quickly. If a tool is not present immediately after you connect, it is still installing; check /var/log/greplabs-detection-install.log.

All non-Kali nodes:

  • git
  • VS Code
  • Ollama

Ubuntu (general-purpose Linux workstation):

  • XFCE desktop and xrdp
  • Browsers: Chromium, Firefox
  • Build toolchains: build-essential (gcc/g++/make), Python (pip, venv)
  • Backgrounded toolchains: Docker CE, Node.js/npm, JDK + Maven, .NET SDK 8.0, Go, Rust
  • Detection: auditd (with ruleset), osquery, Wireshark, mitmproxy
  • SBOM and vulnerability scanning: Syft, Grype, Trivy
  • Reverse engineering (cross-platform set):
    • Ghidra (disassembler/decompiler)
    • rizin (radare2 fork)
    • ILSpy CLI / ilspycmd (.NET decompiler)
    • jadx (Android/Java DEX-to-Java decompiler)
    • jd-gui (Java decompiler)
    • Detect It Easy / DIE (packer and compiler identification)
    • Nodejs-Tracer (Frida-based Node.js tracer, cloned not installed)

Windows Server 2022 and Windows 11 (both share the same provisioning script):

  • git, VS Code, Ollama
  • .NET SDK 8.0, Node.js
  • Browser: Google Chrome
  • Detection: Sysmon (with SwiftOnSecurity config), Procmon, Autoruns, Regshot
  • IE Enhanced Security Configuration disabled (Server only)
  • Reverse engineering set:
    • x64dbg (debugger)
    • dnSpyEx (.NET decompiler/debugger, both x64 and x86 builds)
    • de4dot (.NET deobfuscator, GDATA's maintained de4dotEx build)
    • Detect It Easy / DIE (packer and compiler identification)
    • System Informer (formerly Process Hacker)
    • Sysinternals suite (full)
    • jd-gui (Java decompiler)
    • Ghidra (disassembler/decompiler)
    • Nodejs-Tracer (Frida-based Node.js tracer, cloned not installed)
  • C++ build tools (MSVC) are NOT provisioned. The Visual Studio Build Tools install is large and slow. Install manually if needed: winget install Microsoft.VisualStudio.2022.BuildTools
  • Binary Ninja and 010 Editor are NOT provisioned. Both are commercial and license-locked. Install them yourself and supply your own license.

Kali Linux (adversary / threat-intelligence node):

  • Provisioned via cloud-init, not a VM extension
  • XFCE desktop and xrdp
  • Firefox-ESR with telemetry disabled and ransomware leak-site bookmarks (RansomLook, ransomware.live)
  • Tor Browser, GPG-verified at install, with the same leak-site bookmarks
  • Kali's standard offensive toolset (ships with the image)

Language models

Ollama is installed but models are not pulled during provisioning, since the downloads are large enough to risk timing out the extension. Pull them after first login:

ollama pull gemma4:e4b
ollama pull R4C3R/minicpm5-1b-fable5-heretic

I chose these because they fit my intended use case, but you can choose whatever you want for this lab.

The default Standard_D2s_v5 size (2 vCPU, 8 GB RAM) runs small models comfortably. Reverse engineering with a debugger and disassembler open is heavier; if the boxes feel sluggish, bump vm_size in terraform.tfvars (for example to Standard_D4s_v5). All VMs share this size.

User Accounts

Each non-Kali box is provisioned with two accounts:

  • labmin: the administrator, used for provisioning and management.
  • nottavictim: a non-privileged account (no sudo on Linux; Users group only on Windows). It is the baseline foothold an attacker would land on. What you do with it, privilege-escalation practice, detection tuning, is left to you.

The nottavictim password comes from the user_password variable and is injected at deploy time (Linux via templatefile; Windows via the extension's encrypted protected_settings), so it never appears in the scripts hosted on GitHub. The Kali node is the attacker box and deliberately does not get this account.

Cost

This lab bills by the hour. Destroy it when you are not using it, for your sanity and your wallet.

Roughly $0.75 to $0.90 USD per hour in centralus, broken down as:

  • 4 D2s_v5 VMs, about $0.10/hr each, region-dependent
  • Azure Bastion Standard SKU, about $0.29/hr
  • OS disks (64 GB StandardSSD on the Linux nodes, larger defaults on Windows), a few cents per hour
  • Static public IP, about $0.005/hr

Free: resource group, virtual network, subnets, and network security group

Verify against the Azure pricing calculator for your region and subscription.

Notes:

  • Bastion bills simply for existing, whether or not you are connected through it
  • OS disks bill even while a VM is stopped; stopping is not the same as destroying
  • terraform destroy removes everything, including disks

A four-hour session costs a few dollars. A forgotten week costs well over $100 USD, and grepLabs is pricier than a three-VM lab, so do not leave it running.

Security Notes

Please keep in mind that this is a lab. I deliberately chose convenience over hardening for several things:

  • The nottavictim password is injected via Terraform and never committed to the public scripts, but like admin_password, it lives in state as plaintext. On Windows it is passed through the extension's encrypted protected_settings rather than the GitHub-fetched script, so the secret never touches the public file.
  • Kali is a required marketplace image, which carries withdrawal risk. If the Kali image is pulled from the Azure Marketplace, the apply fails. There is no fallback flag; grepLabs assumes the image is available. If you need a version that survives withdrawal, use CICDefense, which builds its adversary node from stock Ubuntu.
  • The Kali cloud-init downloads Tor Browser and GPG-verifies its signature, but installs it even if verification fails, logging the outcome. This is a deliberate burn-box posture: the Kali node is isolated, destroyable, and reached only through Bastion, so an unverified Tor Browser on it is an acceptable risk for a lab. Do not carry this pattern into anything that matters.
  • The Windows provisioning script is fetched from a public GitHub raw URL (raw.githubusercontent.com/grepstrength/grepLabs/main/scripts/win-setup.ps1) at deploy time and executed. It is pinned to a branch, not a commit, so the same config can pull a different script over time. This is a live example of the remote-fetch supply-chain risk this lab studies.
  • Several reverse engineering tools are installed from GitHub release assets resolved at deploy time and from vendor scripts, rather than signed distro packages. This is convenient and common, and it is also exactly the kind of unverified-supply-chain step this lab exists to study.
  • Password authentication is enabled on the Linux VMs. SSH keys are the commonly accepted practice. This is defensible only because the VMs have no public IP and are reachable solely through Bastion. To switch, replace disable_password_authentication = false with an admin_ssh_key block.
  • Terraform state contains secrets in plaintext. Marking a variable sensitive only redacts it from console output. State files are gitignored, but a production setup would use a remote backend with encryption at rest and state locking.
  • Provisioning scripts use curl piped to shell. The Ollama installer executes a remote script unreviewed. This is the standard install path and, to be honest, a live example of the type of thing this lab is meant to study.
  • Image versions are set to latest rather than pinned. This is done only for convenience. The result is that the same configuration produces different images over time. If you want completely reproducible builds, pin the exact version you want.
  • Egress is unrestricted. Adding outbound deny rules to the NSG is a natural hardening exercise, and it is the control that would stop a compromised host from exfiltrating secrets.

Never commit terraform.tfvars or .tfstate files. Both are gitignored at the repo root.

Troubleshooting

terraform plan prompts for a variable. A required variable has no value. Check that terraform.tfvars exists and is filled in.

Subscription ID is not known by Azure CLI. The GUID in terraform.tfvars does not match your logged-in session. Run az account list --all --output table and compare. Also confirm you are logged into the correct account, especially if you use more than one.

SkuNotAvailable or NotAvailableForSubscription. Your regional vCPU quota is likely zero or too low, or the VM size family is not offered to your subscription in that region. Check with az vm list-usage --location <region> --output table and look at the Limit column. Fix by requesting a quota increase (Portal, search Quotas, Compute) or by choosing a region or size that is available to your subscription.

Kali apply fails with a marketplace or purchase error. The Kali image may have been withdrawn from the Azure Marketplace, or its plan terms need accepting. Kali is required in grepLabs, so there is no flag to disable it. Options: confirm the image is still published with az vm image list --publisher kali-linux --offer kali-linux --location centralus --all --output table, accept the marketplace terms manually with az vm image terms accept, or use the CICDefense repo, which builds its adversary node from stock Ubuntu and does not depend on a marketplace image.

PlatformImageNotFound. A marketplace image reference has drifted. Verify with:

az vm image list --publisher <publisher> --offer <offer> --location centralus --all --output table

Bastion Connect pane only shows SSH for a Linux VM. The RDP protocol option requires the Bastion Standard SKU (or higher). On the Basic SKU, the portal only offers SSH for Linux VMs, so you get a terminal instead of the XFCE desktop. Set sku = "Standard" on the azurerm_bastion_host resource and re-apply. The SKU can be upgraded in place (no rebuild).

az network bastion tunnel fails: "Bastion Host SKU must be Standard or Premium and Native Client must be enabled." Same root cause as above. Native-client tunneling needs the Standard (or Premium) SKU and tunneling_enabled = true on the azurerm_bastion_host resource. Set both and re-apply, then re-run the tunnel command.

Extension fails with "VM has reported a failure". Terraform cannot see inside the script. Connect via Bastion and read the logs. On Linux, /var/log/azure/custom-script/handler.log. On Windows, read stderr under C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\*\Downloads\.

Kali comes up but has no desktop, or cloud-init did not finish. Kali provisions via cloud-init, not an extension, so its logs are different. Connect and check /var/log/cloud-init-output.log and /var/log/greplabs-torbrowser.log. Cloud-init reboots the box when it finishes, so give it a few minutes after first boot.

Blank grey screen after RDP to a Linux VM. The .xsession file is missing or has the wrong owner. Check that the provisioning wrote it for the correct user.

templatefile error: "vars map does not contain key ...". A provisioning script or cloud-init file uses a bash variable with brace syntax (${VAR}), which collides with Terraform's templatefile() interpolation. Escape it as $${VAR} so Terraform passes it through to bash. Only Terraform-injected variables like ${admin_username} should remain single-dollar.

A GitHub-sourced RE tool did not install. The Linux RE tools resolve their download URLs from the GitHub API at deploy time. Unauthenticated GitHub API calls are rate-limited to 60 per hour per IP; if several boxes provision at once you can brush that limit and a resolve returns empty. Re-run the specific install by hand, or space out your applies.

Windows extension fails with a 404 / "failed to download the blob". The Windows extension fetches scripts/win-setup.ps1 from the public GitHub raw URL at deploy time. If you edited the script but did not push, or the filename in extensions.tf (fileUris + -File) does not match the actual file, the download 404s. Confirm the raw URL resolves in a browser before applying, and make sure the file, the fileUris URL, and the commandToExecute -File argument all say win-setup.ps1.

Nothing else works. Turn on debug logging:

$env:TF_LOG = "DEBUG"
terraform plan
$env:TF_LOG = ""

Roadmap

Note: This was all what I planned to add to CI/CDefense, but realized it would take more effort to comb through every part I needed to change as opposed to just starting relatively fresh in a new repo.

  • Detection and logging layer: Sysmon on Windows; auditd and osquery on the Linux box; SBOM and vulnerability scanning with Syft, Grype, and Trivy
  • Reverse engineering toolsets on the Windows and Ubuntu boxes
  • Kali adversary/threat-intel node with Tor Browser and leak-site bookmarks, provisioned via cloud-init
  • Non-privileged nottavictim account on the runners (attacker-foothold baseline)
  • Seeded vulnerable pipeline for attack scenarios (this is where the CircleCI/GitHub dummy accounts come in)
  • Remote state backend with locking
  • Egress filtering exercise
  • Pinned image versions and commit-pinned script fetches for reproducible builds
  • Central log aggregation so detection events from all four VMs land in one place

License

Free-for-all, aka MIT.