Hitchhiker's Guide — Enumerate, Find the Path, Harden It, Write the Detection

The operator/detection-engineer range walkthrough for Phase 04. You stand up an owned range, seed realistic misconfigurations, run the analyzer labs against synthetic snapshots of those misconfigs, walk the conceptual escalation path, then harden the misconfiguration and write the Sysmon/auditd detection rule. You are building the per-foothold finding that goes in the engagement report.

Safety (non-negotiable). Everything here runs on an isolated, owned range you built. The labs are analyzers over synthetic host facts — Python dicts that describe what enumeration tools would report on a seeded lab VM. There is no live exploitation, no service write, no token theft, no GTFOBins one-liner run against any process. The escalation steps are described conceptually so you can document the path and write the detection. Never run any of this on a host you do not own or have explicit written authorization to test.

Table of Contents


0. Authorize and scope

Before anything boots, write the five facts in the range notebook:

  • Owner: you. The range VMs are yours; no shared or corporate domain.
  • Scope: the named lab VMs only, on an isolated vSwitch with deny-by-default egress.
  • Methods: lab code (Python analyzers over synthetic dicts), concept-level escalation walk-through, hardening commands on your own VMs, Sysmon/auditd configuration changes. No live exploitation, no service modification on a host you do not own.
  • Data: synthetic. No real credentials, PII, or production data in any fixture or screenshot.
  • Stop conditions: any unexpected egress; any command that would write to, modify, or exploit a real service process; any command run against infrastructure you do not own → stop, snapshot, investigate.

1. Build the range (Windows + Linux VMs)

A minimal free range for both OS tracks:

        isolated vSwitch (host-only, deny-by-default egress)
       /                                    \
  Windows 10/11 VM                       Ubuntu 22.04 (or CentOS) VM
  - Sysmon installed, community config   - auditd installed and enabled
  - PowerShell 5.1 + WinPEAS + Seatbelt  - LinPEAS + pspy
  - Seeded misconfigs (§2)               - Seeded misconfigs (§2)
  - Windows Event Forwarding → SIEM      - auditd log → SIEM / local /var/log/audit/audit.log

For the detection stack, Wazuh or Elastic (both free) can ingest both Windows event logs (via the agent or WEF) and Linux auditd logs (via the Wazuh agent or Filebeat). This lets you write Sigma rules and see them fire in a dashboard.

Snapshot every VM in a clean state before seeding. Each exercise starts from the snapshot; you seed, enumerate, document, harden, verify, then revert.


2. Seed the misconfigurations

These are realistic misconfigurations you deliberately introduce on your own VMs, one at a time. Introduce, snapshot, enumerate from that state, document the finding, harden, verify, revert. Do not leave multiple misconfigs active simultaneously — you need a clean single-variable experiment per finding.

Windows seeds (choose one per exercise session)

Seed A — Unquoted service path with writable intermediate dir.

On your lab Windows VM (elevated PowerShell):

# Create a fake service binary in a path with spaces
mkdir "C:\Program Files\Acme Logger"
copy C:\Windows\System32\cmd.exe "C:\Program Files\Acme Logger\logger.exe"

# Register service with unquoted path (the bug) — sc creates the registry entry
sc create AcmeLogger binPath= "C:\Program Files\Acme Logger\logger.exe" start= auto obj= LocalSystem

# Make C:\ writable by Users (the precondition — on a real host this would be a build artifact)
icacls C:\ /grant "BUILTIN\Users:(OI)(CI)(W)"

This seeds an unquoted service path (C:\Program Files\Acme Logger\logger.exe) where the SCM would try C:\Program.exe first, and where Users can write to C:\.

Seed B — Weak service DACL.

sc create WeakSvc binPath= "C:\Windows\System32\cmd.exe /k" start= auto obj= LocalSystem
# Grant SERVICE_CHANGE_CONFIG (0x0002) to Authenticated Users
sc sdset WeakSvc "D:(A;;RPWPRCWD;;;AU)(A;;CCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCLCSWRPWPDTLOCRSDRCWDWO;;;BA)"

Seed C — Dangerous privilege on a user account.

# Add SeImpersonatePrivilege to a test user via Local Security Policy (secpol.msc) or
# directly via ntrights.exe:
# ntrights.exe +r SeImpersonatePrivilege -u labuser

Linux seeds (choose one per exercise session)

Seed A — Dangerous SUID binary.

# On your lab Linux VM as root:
cp /usr/bin/python3 /usr/local/bin/python3-lab
chown root:root /usr/local/bin/python3-lab
chmod u+s /usr/local/bin/python3-lab   # set SUID bit

Seed B — sudo NOPASSWD on GTFOBins binary.

# Add to /etc/sudoers.d/lab (as root):
echo "labuser ALL=(root) NOPASSWD: /usr/bin/vim" > /etc/sudoers.d/lab
chmod 440 /etc/sudoers.d/lab

Seed C — Dangerous capability.

# Grant cap_setuid to python3:
setcap cap_setuid+ep /usr/bin/python3

Seed D — Writable cron script.

# Create a root cron script world-writable (deliberate misconfig):
echo '#!/bin/bash\ndate >> /var/log/backup.log' > /etc/cron.daily/backup-lab.sh
chmod 777 /etc/cron.daily/backup-lab.sh

3. Enumerate and snapshot the host facts

After seeding, enumerate as the low-privileged lab user. Capture the output as the Python dict that feeds the lab analyzer.

Windows enumeration → snapshot dict

# As labuser (non-admin):

# Services:
sc query
sc qc AcmeLogger
icacls "C:\Program Files\Acme Logger\logger.exe"

# Privileges:
whoami /priv

# Registry autoruns:
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"

# Scheduled tasks:
schtasks /query /fo LIST /v

Translate the output into a Python dict matching Lab 01's host_facts schema. The dict represents "what WinPEAS/Seatbelt would report" — not what the actual binary state is, but a structural description of the misconfigurations.

host_facts = {
    "services": [
        {
            "name": "AcmeLogger",
            "image_path": "C:\\Program Files\\Acme Logger\\logger.exe",
            "run_as": "LocalSystem",
            "binary_writable_by": [],
            "writable_intermediate_dirs": ["C:\\"],
            "dacl": [],
        }
    ],
    "token_privileges": ["SeChangeNotifyPrivilege"],   # no SeImpersonate yet
    "autoruns": [],
    "scheduled_tasks": [],
}

Linux enumeration → snapshot dict

# As labuser:
find / -perm -4000 -type f 2>/dev/null
getcap -r / 2>/dev/null
sudo -l
ls -la /etc/cron.daily/
cat /etc/crontab
echo $PATH

Translate into Lab 02's facts schema:

facts = {
    "suid_files": [
        {"path": "/usr/local/bin/python3-lab", "owner": "root"},
        {"path": "/usr/bin/ping", "owner": "root"},
    ],
    "capabilities": [],
    "sudo_rules": [],
    "writable_path_dirs": [],
    "cron_files": [],
}

4. Run the analyzer labs against the snapshot

Feed the snapshot dict into the lab:

# From the repo root:
cd red-team-engineer/phase-04-os-internals-privesc/lab-01-windows-privesc-analyzer
LAB_MODULE=solution python3 -m pytest -q     # verify reference passes
python3 -c "
import solution
import json

host_facts = {
    'services': [
        {
            'name': 'AcmeLogger',
            'image_path': 'C:\\\\Program Files\\\\Acme Logger\\\\logger.exe',
            'run_as': 'LocalSystem',
            'binary_writable_by': [],
            'writable_intermediate_dirs': ['C:\\\\'],
            'dacl': [],
        }
    ],
    'token_privileges': [],
    'autoruns': [],
    'scheduled_tasks': [],
}
findings = solution.analyze(host_facts)
for f in findings:
    print(json.dumps(f, indent=2))
"

The analyzer should return a finding for the unquoted service path with:

  • technique: T1574.009
  • path_to_system: a description of the C:\Program.exe interception path
  • detection: the Sysmon EID 1 rule / Security EID 7045 description

For Linux:

cd ../lab-02-linux-privesc-auditor
LAB_MODULE=solution python3 -m pytest -q
python3 -c "
import solution, json
facts = {
    'suid_files': [{'path': '/usr/local/bin/python3-lab', 'owner': 'root'}],
    'capabilities': [], 'sudo_rules': [], 'writable_path_dirs': [], 'cron_files': [],
}
for f in solution.audit(facts):
    print(json.dumps(f, indent=2))
"

The auditor should return a finding for the dangerous SUID python3-lab with:

  • technique: T1548.001
  • escalation_note: the os.execl('/bin/sh', 'sh', '-p') concept
  • detection: the auditd execve -F euid=0 -F ruid!=0 rule

This lab output IS the finding. It is the machine-readable first draft that you will refine into the engagement report finding document.


5. Walk the conceptual escalation path

For each finding the analyzer returns, document the conceptual escalation path in the finding template. This is not running the exploit — it is describing the steps in enough detail that a remediation engineer can understand what needs to be fixed.

Windows — Unquoted service path (T1574.009) conceptual path:

1. Attacker has code execution as labuser (low-priv, standard user)
2. Identify: sc qc AcmeLogger → ImagePath = C:\Program Files\Acme Logger\logger.exe (unquoted)
3. Identify precondition: icacls C:\ → BUILTIN\Users:(W) — writable by low-priv user
4. Place: write an executable to C:\Program.exe (the higher-precedence prefix SCM will try first)
5. Trigger: next service start (sc start AcmeLogger, or system reboot)
6. Result: SCM resolves C:\Program.exe first, launches it as LocalSystem → attacker code runs as SYSTEM

Linux — Dangerous SUID (T1548.001) conceptual path:

1. Attacker has shell as labuser (non-root)
2. Identify: find / -perm -4000 → /usr/local/bin/python3-lab (SUID, owned by root)
3. Cross-reference: GTFOBins confirms python3 with SUID → os.execl('/bin/sh','sh','-p') gives root shell
4. Execute: python3-lab -c "import os; os.execl('/bin/sh', 'sh', '-p')"
5. Result: execve sets euid=0 (SUID bit), os.execl launches /bin/sh with -p (preserve euid) → shell runs as root

Write this in the finding.md template from red-team-engineer/templates/finding.md, filling in the ATT&CK mapping, precondition, narrative, and detection opportunity fields.


6. Harden the misconfiguration

Hardening is the remediation column in the finding. On your range VM, implement it and verify the path no longer works conceptually.

Windows hardening

Fix for unquoted service path:

# Quote the ImagePath:
sc config AcmeLogger binPath= '"C:\Program Files\Acme Logger\logger.exe"'
sc qc AcmeLogger  # verify the path is now quoted

# Remove the writable precondition:
icacls C:\ /remove "BUILTIN\Users" /T
# Verify:
icacls C:\

Fix for weak service DACL:

# Remove SERVICE_CHANGE_CONFIG from Authenticated Users — reset to safe default
sc sdset WeakSvc "D:(A;;CCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCLCSWRPWPDTLOCRSDRCWDWO;;;BA)"

Fix for dangerous privilege on user:

# Local Security Policy → User Rights Assignment → remove SeImpersonatePrivilege from the test user
# or via secedit, or remove the user from any group that grants the right

Linux hardening

Remove SUID bit:

chmod u-s /usr/local/bin/python3-lab
ls -la /usr/local/bin/python3-lab  # verify: -rwxr-xr-x (no s)

Remove dangerous capability:

setcap -r /usr/bin/python3
getcap /usr/bin/python3  # should return empty

Fix sudo misconfig:

# Remove the overly permissive rule:
rm /etc/sudoers.d/lab
# Verify:
sudo -l  # should no longer list vim

Fix writable cron script:

chmod 755 /etc/cron.daily/backup-lab.sh
chown root:root /etc/cron.daily/backup-lab.sh
ls -la /etc/cron.daily/backup-lab.sh  # verify

7. Write the Sysmon / auditd detection and verify it fires

Windows — Sysmon rule for services spawning shells (covers token impersonation + service hijack)

Add to your Sysmon config XML:

<RuleGroup name="privesc-service-shell" groupRelation="or">
  <ProcessCreate onmatch="include">
    <!-- Service process (services.exe parent) spawning an interactive shell -->
    <ParentImage condition="image">services.exe</ParentImage>
    <Image condition="image">cmd.exe</Image>
  </ProcessCreate>
  <ProcessCreate onmatch="include">
    <ParentImage condition="image">services.exe</ParentImage>
    <Image condition="image">powershell.exe</Image>
  </ProcessCreate>
</RuleGroup>

Reload Sysmon: Sysmon.exe -c sysmonconfig.xml

Sigma rule (for SIEM ingestion):

title: Service Spawning Interactive Shell — Privilege Escalation
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: >
  The Service Control Manager process (services.exe) spawned an interactive shell.
  Consistent with token impersonation (potato family), service binary replacement,
  or scheduled-task misuse. Legitimate services do not spawn cmd.exe or powershell.exe.
logsource:
  product: windows
  category: process_creation
detection:
  selection:
    ParentImage|endswith: '\services.exe'
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
  condition: selection
falsepositives:
  - Some MSI-based service installers briefly spawn cmd.exe during install; tune by Image path
    and CommandLine pattern.
level: high
tags:
  - attack.privilege_escalation
  - attack.t1134.001
  - attack.t1543.003

Verify on range: On the Windows VM (clean snapshot + Sysmon config loaded), open an elevated command prompt and run sc start <any service>. Then simulate the suspicious parent relationship by running a test:

# Benign simulation: not actual exploitation — just verify the rule structure by
# checking that a cmd.exe spawned from services.exe context would appear in the log.
# The actual verification is through a process injection simulator or Atomic Red Team test T1134.

Check the Sysmon operational log (Event Viewer → Applications and Services → Microsoft-Windows-Sysmon/Operational → filter EID 1) for the ParentImage/Image combination.

Linux — auditd rules for SUID execution and setuid(0)

Add to /etc/audit/rules.d/privesc.rules:

# SUID execution: euid=0 by a non-root ruid
-a always,exit -F arch=b64 -S execve -F euid=0 -F ruid!=0 -k suid_exec

# Capability-based root: setuid(0) called by any process
-a always,exit -F arch=b64 -S setuid -F a0=0 -k setuid_root

# Write to cron files by non-root:
-a always,exit -F arch=b64 -S openat -F path=/etc/cron.daily -F perm=w -F auid!=0 -k cron_write
-a always,exit -F arch=b64 -S openat -F path=/etc/crontab -F perm=w -F auid!=0 -k cron_write

# sudo invocations (also in auth.log, but auditd covers no-PAM paths):
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/sudo -k sudo_exec

Reload: augenrules --load or service auditd restart.

Verify: On your lab Linux VM (with the python3-lab SUID seed still present):

# As labuser — executing the SUID binary should fire the suid_exec rule:
/usr/local/bin/python3-lab -c "exit(0)"  # benign, just launches the SUID binary

# Check the audit log:
ausearch -k suid_exec | grep python3-lab

You should see an audit record with euid=0 ruid=<labuser-uid> and the suid_exec key. This confirms the rule fires on a SUID execution, even without the escalation step.

After hardening (chmod u-s), re-run the same command — the audit record should now show euid=<labuser-uid> ruid=<labuser-uid> (no longer euid=0), confirming the control is effective.


8. The evidence packet (your finding document)

For each misconfiguration, collect and hash (in the range, never published raw):

  • The enumeration output that discovered the misconfiguration (raw tool output, sanitized).
  • The Lab 01 / Lab 02 analyzer output — the finding with ATT&CK ID, path-to-SYSTEM/root, and detection description.
  • The conceptual escalation path (§5 format).
  • The hardening action taken and its verification output.
  • The Sysmon/auditd rule written.
  • The verification screenshot: the audit rule firing on the benign SUID execution / Sysmon EID 1 on the simulated parent-child relationship.
  • A one-paragraph narrative: the misconfiguration, the privilege it provides, the telemetry footprint, the hardening, and the detection — observation separated from inference.

This is the Phase 04 portfolio artifact and the input to the Cedar Lattice finding report (templates/finding.md).


9. Teardown and cost control

  • Revert every VM to the pre-seed clean snapshot. Misconfigurations should not persist between exercises — each session starts from the clean baseline.
  • Remove any seeds you applied manually if you did not revert (remove the test service, restore file permissions, remove the sudo rule, remove capability).
  • If the range is cloud-based: destroy the VMs after the session and confirm billing returns to baseline. Set a budget cap before first boot (Phase 00 rule).
  • Purge raw logs and enumeration output that are not part of the evidence packet.
  • Confirm no egress occurred to any non-lab network during the session.

Common false claims

"I escalated to SYSTEM." On this range you ran a Python analyzer over a synthetic host-facts dict and documented the conceptual path. The honest claim: "I identified a misconfiguration, classified it with the correct ATT&CK technique, documented the path to SYSTEM, implemented the hardening, and wrote the detection that catches it." That is what the engagement report says.

"The misconfiguration is just an unquoted path — the risk is low." An unquoted path with a writable intermediate directory is an unconditional code-as-SYSTEM path. Risk is not in the trivia of a missing quote mark; it is in the write-access precondition. Always verify the precondition before classifying severity.

"The detection I wrote is complete." A detection written on paper is a hypothesis. On this range you verified it fires on a benign execution that triggers the same observable footprint. That is necessary. It is not sufficient: in production you also need FP tuning, a documented allowlist, ownership assignment, and a regression test. Note what additional work remains.

"Hardening means removing the SUID bit from all system binaries." Remove only the binaries that are misconfigured — non-standard additions or standard binaries that do not need the bit for their function. Mass removal of SUID bits breaks legitimate system operations (sudo, mount, su, newgrp). Targeted hardening is the deliverable.

"Linux capabilities are an advanced, rare finding." getcap -r / on a typical Ubuntu 22.04 install returns several binaries with capabilities (ping has cap_net_raw, python3 may have capabilities from a careless install). If your enumeration does not include getcap, you will miss this entire class.