Analyst Field Guide · Detection Engineering
A detection is a bet: that a pattern you can describe will fire on real threats more often than on everything else. Every rule you write trades false alarms against missed attacks — you cannot drive both to zero, and until a rule has been tested against real data, you don't know either rate. This guide is how to write detections that earn their place, how to reason about the tradeoff that governs all of them, and how to stay honest about what a rule does and doesn't catch.
Detection engineering is turning knowledge of attacker behavior into code that fires when that behavior appears — and keeping it working as both the environment and the adversary change. It borrows the discipline of software engineering: detections are written, reviewed, version-controlled, tested, and maintained. The single idea that separates a detection engineer from someone who "writes alerts" is refusing to treat a rule as done when it's written. A rule is a hypothesis until it's been tested, and a liability once it's been forgotten.
Detection engineering is a loop, not a launch. You start from a hypothesis about a threat behavior — ideally anchored to a known technique (the Attack Taxonomy guide's ATT&CK mapping) — build a rule to catch it, test it against both benign and malicious data, deploy it, tune out the noise, and keep revisiting it as it decays. Skip the testing and tuning stages and you've built an alert cannon, not a detection.
This is the tradeoff underneath everything else, so it comes first. A rule either fires or stays silent; the activity either was or wasn't the thing you care about. That's four outcomes — two good, two costly — and the two costly ones pull against each other.
The practical consequence: tie a rule's aggressiveness to the cost of each error for that specific threat. For ransomware execution you may accept more false positives to cut misses to near-zero; for a low-severity policy check you tune hard for precision so analysts trust it. There's no universal setting — only a defensible choice per detection.
Not all detections are equally durable. David Bianco's Pyramid of Pain ranks indicators by how much it costs the adversary to change them once you're detecting on them — and that cost is a direct proxy for how long your detection will keep working.
This is why "detect behavior, not hashes" is the field's mantra. It doesn't mean IOCs are useless — a known-bad hash or domain is free coverage worth having — it means the detections you build and maintain should climb toward TTPs, where one good rule covers a whole class of tooling.
Different data surfaces need different detection languages. The three you'll meet most sit over the network, over files and memory, and over logs — and they map neatly onto surfaces covered elsewhere in this collection.
Every rule shown next is illustrative and untested — written to show structure, not validated against any real dataset. Treat each as a starting point whose false-positive and false-negative behavior is unknown until you test it (§8), never as a drop-in production detection.
Sigma is a vendor-neutral YAML format for log-based detections. You write the logic once; a converter (pySigma / sigma-cli) compiles it to your SIEM's query language. Its structure reads top-down: what logs, what to match, and — importantly — a declared list of known false positives.
title: Encoded PowerShell Command Line
status: experimental
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains: '-enc'
filter_admin:
ParentImage|endswith: '\ccmexec.exe'
condition: selection and not filter_admin
falsepositives:
- Admin or deployment tooling using encoded commands
level: medium
tags:
- attack.execution
- attack.t1059.001Note the pieces that matter beyond the match itself: pipe modifiers (|endswith, |contains; also |startswith, |re for full regex, |all) shape how values match; the condition combines named selections and filters with and/or/not and forms like 1 of selection_*; and falsepositives plus level carry the operational knowledge a triaging analyst needs.
False positives: legitimate encoded-command use — patch and deployment tooling, admin automation, some management agents. The filter_admin exclusion is a token gesture; real tuning needs your environment's actual parents. False negatives: obfuscation that avoids the literal -enc substring (PowerShell accepts abbreviations like -e/-encod), execution through other binaries, or download-and-run patterns that never touch that flag. The rule detects one spelling of one behavior, not "malicious PowerShell." Its real FP and FN rates are unknown until measured against your logs.
YARA matches patterns in files and process memory — the workhorse for classifying malware samples and scanning endpoints. A rule is a name, an optional meta block, a strings block, and a required condition.
rule Illustrative_PE_With_Shell_Marker
{
meta:
description = "Illustrative only - not a production rule"
author = "field-guide"
strings:
$mz = { 4D 5A } // "MZ" - DOS/PE header bytes
$s = "cmd.exe /c" nocase
condition:
$mz at 0 and $s
}Strings can be text (with modifiers nocase, wide, ascii, fullword, xor, base64), hexadecimal byte patterns like { 4D 5A } (which can include wildcards and jumps), or regular expressions. The condition is boolean logic over those strings plus positional and set operators ($mz at 0, any of them, N of ($s*), filesize). Note: YARA-X, VirusTotal's Rust rewrite, is the newer engine alongside the long-standing YARA 4.x line — verify behavior against whichever you run.
False positives: enormous. Vast numbers of benign Windows executables begin with MZ and legitimately contain cmd.exe /c (installers, admin utilities, launchers). As written this would match huge swathes of clean software — it is a teaching skeleton, not a detection. False negatives: any sample that doesn't carry that exact literal — packed, encrypted, or obfuscated binaries where the string isn't in cleartext, or malware that shells out differently. A usable YARA rule needs strings genuinely distinctive to the target, and testing against both a clean-file corpus (for FP) and real samples (for FN).
Snort and Suricata inspect packets and flows. Their rules share a syntax: a header (action, protocol, source and destination with ports, and direction) followed by options in parentheses (the match criteria and metadata). Suricata is the multithreaded engine from OISF and is largely Snort-rule-compatible.
alert tcp $HOME_NET any -> $EXTERNAL_NET any (
msg:"ILLUSTRATIVE - suspicious URI substring";
flow:to_server,established;
service:http;
http_uri;
content:"/a1b2beacon",nocase;
sid:1000001;
rev:1;
)Reading it: alert is the action (also log, pass, and inline drop/reject); -> is direction (<> is bidirectional); flow scopes to established client-to-server traffic; the http_uri sticky buffer restricts the following content match to the request URI; sid is a unique id (use values above 1,000,000 for custom rules) and rev is the revision, bumped on every change. Other common options include pcre, fast_pattern, reference, classtype, and rate controls like detection_filter.
False positives: any legitimate request whose URI contains that substring; and if the content string were more generic, it would match constantly. False negatives — decisive here: the request URI is only visible on plaintext HTTP. Over HTTPS the URI is inside TLS (see the HTTP and Reading-the-Wire guides), so this rule sees nothing without decryption — which covers most modern traffic. It also misses any other path the adversary chooses. A single hardcoded URI is exactly the brittle, bottom-of-the-pyramid indicator §3 warned about. Real rates are unknown until tested against representative traffic.
This is the section the whole guide is built around. A detection that has never been run against real data has unknown false-positive and false-negative rates — not low ones, not acceptable ones, unknown ones. "It looks right" is not a measurement.
Validation has two halves, and skipping either leaves you blind. Test the false-positive rate by running the rule against a benign, production-representative baseline and measuring how often it fires on normal activity — a rule that lights up on everyday behavior will be muted by analysts within a week. Test the false-negative rate by running it against data where you know the behavior is present: real samples, replayed attack traffic, or a controlled emulation of the technique (frameworks like Atomic Red Team exist for exactly this). A rule that doesn't fire on the very thing it targets is worse than none, because it creates false assurance. Treat detections as code: version-control them, peer-review the logic, and automate these tests where you can. Until a rule has passed both, mark it clearly as not validated against real data — and never let it stand behind an unqualified claim that a threat "is covered." A deployed detection reduces risk for the specific behavior it was tested against; it does not make you secure, and it leaves every untested variant and every out-of-scope technique open.
The same skepticism applies to the detour of "the rule fired in the lab, so it works." Firing on your one hand-crafted test proves it can fire — not that it fires on real variants, nor that it stays quiet on real benign traffic. Both rates are properties of the rule in your environment, and both drift over time (§9).
A deployed rule is the start of its working life, not the end. Tuning cuts noise without opening blind spots; maintenance fights the decay that eventually degrades every detection.
| Practice | What it does · the trap |
|---|---|
| Allowlisting / filters | Exclude known-benign sources of a match. Trap: every exclusion is a hole an adversary can hide in — allowlist narrowly and document why. |
| Thresholds & rate limits | Fire only after N occurrences in a window (e.g. failed logons). Trap: raises false negatives — low-and-slow activity stays under the bar. |
| Severity / routing | Match the alert's level to response urgency so analysts triage by real priority (ties to Incident Response). Trap: everything-is-critical trains people to ignore it. |
| Drift & decay review | Re-validate on a schedule. Log formats change, software updates rename fields, new benign patterns appear, adversaries adapt — a good rule rots into noise or blindness. |
| Metrics | Track precision, alert volume, and time-to-detect per rule. Trap: counting alerts fired rewards noisy rules; measure true positives and analyst outcomes. |
| Documentation | Record what each rule detects, its known false positives, its gaps, and its data dependency. An undocumented rule can't be tuned or trusted by the next analyst. |
Retirement is part of the lifecycle too. A rule whose technique is now covered better elsewhere, or whose data source is gone, should be removed — dead rules add noise and maintenance cost while giving a false sense of coverage.
| Belief | Reality |
|---|---|
| "The rule is written, so the threat is covered" | A rule is a hypothesis until tested against real benign and malicious data. Untested = unknown FP and FN (§8). |
| "A good rule has no false positives" | Every rule sits on the FP/FN tradeoff. Zero FP usually means it's tuned so tight it misses real variants. The goal is a defensible balance, not zero (§2). |
| "It fired in the lab, so it works" | Firing once proves it can fire — not that it catches real variants or stays quiet on real traffic. Both rates are environment properties (§8). |
| "More detections = better coverage" | Noisy or redundant rules cause alert fatigue, which turns false positives into missed real alerts. Fewer, tested, tuned rules beat a pile of untested ones. |
| "Detecting the hash covers the malware" | Hashes change every rebuild. Bottom-of-pyramid indicators are cheap but brittle; durable detection targets behavior/TTPs (§3). |
| "Deploy it and move on" | Rules decay as environments and adversaries change. Detection is maintained on a schedule, not shipped and forgotten (§1, §9). |
Hypothesis (anchor to ATT&CK) → develop → test (FP + FN) → deploy → tune → maintain → revisit. Detection is code: version-controlled, reviewed, tested, and retired when dead.
Every rule balances false positives (noise → alert fatigue → missed real alerts) against false negatives (missed threats). You can't zero both. Base-rate trap: 0.1% FP over a million events = 1,000 false alerts. Tune per threat's error cost.
Pyramid of Pain (Bianco): hashes/IPs/domains are cheap but brittle; artifacts/tools/TTPs are costly for the adversary to change and so more durable. Build and maintain detections up the pyramid; use IOCs for free coverage.
Network → Snort/Suricata. Files & memory → YARA. Logs → Sigma, compiling to SIEM (KQL/SPL/EQL). Same FP/FN discipline in all.
Untested rule = unknown rates; label it "not validated against real data." Test FP against a benign baseline, FN against known-true samples. A deployed detection reduces risk for the tested behavior — it does not make you "secure," and leaves untested variants open.
Rule syntax here reflects current published specifications, verified this session and flagged to re-check before production use: Sigma against the SigmaHQ specification (rule structure, logsource/detection/condition, pipe modifiers, falsepositives, level, ATT&CK tags); YARA against the YARA 4.x documentation and VirusTotal's YARA-X (meta/strings/condition, string modifiers, hex and regex strings); Snort against the Snort 3 rule-writing guide and Suricata/OISF (header plus parenthesized options, sticky buffers such as http_uri, flow, sid/rev). Field names, modifiers, and available keywords change between versions and backends — verify against your engine's current documentation before relying on an exact expression, and do not assume a field or option exists. Every rule shown is illustrative and was not validated against real data; its false-positive and false-negative rates are unknown until tested (§8). ATT&CK technique IDs (e.g. T1059.001) reference the current ATT&CK version — confirm the ID and its tactic mapping against the live matrix. No detection, tuning, or coverage claim in this guide should be read as a security assurance: a tested detection reduces risk for the specific behavior it was validated against and leaves untested variants and out-of-scope techniques uncovered. Companion to the Attack Taxonomy Field Guide (ATT&CK, the source of detection hypotheses), Incident Response (what fires when a detection triggers), Reading a Packet Capture and HTTP & the Application Layer (why network rules go blind under TLS), and the forthcoming Logging & Evidence (the data detections depend on).