CrowdStrike Detection Logic
CrowdStrike Falcon is the most widely deployed modern EDR, and its detection philosophy is the clearest articulation of the “thin sensor, fat cloud” school: the endpoint agent collects and blocks locally where confidence is high, while the detection intelligence — behavioral analytics, correlation, machine learning, human hunting — lives in the cloud. Understanding Falcon means understanding that split, its data model (IOAs and IOCs, Threat Graph joins), its operational surfaces (prevention policies, Real Time Response, OverWatch hunting), and its failure modes — including the one day in July 2024 when a content update taught the entire industry where the single point of failure really was. This module reverse-engineers Falcon’s publicly documented architecture and makes you build a mini Threat Graph so the correlation model stops being marketing and becomes data structures.
Definition
Section titled “Definition”Falcon is CrowdStrike’s platform: a kernel sensor (csagent.sys) plus userland service (CSFalconService) feeding cloud analytics. Its detection logic decomposes into four documented layers:
| Layer | What it is | Where it runs |
|---|---|---|
| IOC matching | Hashes, paths, filenames, domains — explicit indicators | Sensor (immediate) + cloud |
| IOA (Indicators of Attack) | Behavioral sequences evaluated against rules | Sensor (prevention) + cloud (detection) |
| ML classification | On-sensor static models + cloud models over sensor telemetry | Both |
| Threat Graph | Relationship correlation across the fleet, over time | Cloud |
THIN SENSOR / FAT CLOUD ┌────────────────────────────┐ ┌────────────────────────────────┐ │ endpoint │ │ cloud (us-1/us-2/gov [verify]) │ │ csagent.sys (kernel) │ │ ┌──────────────────────────┐ │ │ process/thread/image │ event│ │ IOA analytics │ │ │ handle/file telemetry │─────▶│ │ ML verdicts │ │ │ CSFalconService (user) │HTTPS │ │ Threat Graph correlation │ │ │ local IOA/IOC eval │◀─────│ │ OverWatch human hunting │ │ │ prevention actions │policy│ │ intel: naming/attribution│ │ └────────────────────────────┘ │ └──────────────────────────┘ │ └────────────────────────────────┘CrowdStrike’s threat actor naming taxonomy is real and worth memorizing because it encodes attribution in the name itself: BEAR = Russia (APT29 = COZY BEAR, APT28 = FANCY BEAR), PANDA = China, KITTEN = Iran, CHOLLIMA = DPRK, SPIDER = e-crime (WIZARD SPIDER = the Conti/TrickBot complex), JACKAL = hacktivist/opportunistic, BUFFALO = Vietnam. Malware family names (Win32/<family>) are proprietary [verify current naming] — never fabricate one.
Why It Matters
Section titled “Why It Matters”Falcon is the EDR you are most likely to meet — on red-team engagements, in interviews, and in SOC tooling — and its design choices predict the whole product category: minimal on-disk signatures (so “signature evasion” is nearly a null category against it), behavioral IOAs evaluated both on-sensor (for prevention latency) and in-cloud (for correlation depth), and a graph model where your infrastructure’s reuse across targets is the longest-lived detection surface you have. For defenders, Falcon’s console concepts (prevention vs response policies, sensor update policies, host groups) are the vocabulary of detection engineering at scale, and its data exports (Event Streams API, Falcon Data Replicator) are how serious programs pipe endpoint data into a SIEM with their own rules (BT3). The July 19, 2024 incident — a Falcon content update (channel file C-00000291*.sys [verify exact file naming]) whose bad host-side interpretation caused global Windows BSODs — is a permanent lesson in content-update risk management for any sensor product, and knowing it signals operational maturity.
Detailed Explanation
Section titled “Detailed Explanation”The IOA model: sequences, not strings
Section titled “The IOA model: sequences, not strings”An IOA rule describes a behavior tree: process attributes (signing state, parent, command-line patterns), followed by observation events (memory grants, file writes, script content), correlated within a process lineage. The public mental model:
IOA evaluation (conceptual): trigger: process X allocated RWX in process Y (remote) context: parent chain | prevalence of X | Y's value (lsass?) score: static features + lineage + history verdict: prevent (high conf, policy says so) | detect (scored, queued to graph) | nothing (below threshold — recorded anyway)Three properties drive evasion strategy. First, prevention and detection are separate verdicts: a payload can avoid blocking (below prevention thresholds) while still generating detection telemetry — operators who celebrate “no block” without checking detections have it backwards. Second, the sensor-side evaluation is what produces instant kills; cloud-side correlation is what produces the detection days later when a different host’s events join the graph. Third, the graph is indifferent to tooling: direct syscalls (E4) change which userland frames exist, not the fact that process Y gained a thread from process X.
Threat Graph: the correlation substrate
Section titled “Threat Graph: the correlation substrate”Every event becomes nodes (process, file, domain, user) and edges (spawned, wrote, connected-to, authenticated-as). Detections are subgraph matches: “unsigned process spawned by Office → wrote scheduled task → task executed PowerShell → PowerShell connected to newly-registered domain” is a path query, not a rule list. The operational consequence: your infrastructure is the persistent evidence. A domain registered last week, seen on two clients of one customer, joins the graph across customers (prevalence) [verify cross-customer scope handling — CrowdStrike documents prevalence aggregates, not raw cross-tenant events]. This is why infrastructure rotation discipline matters more per-operation than any single-host technique.
RTR: why “response” changes the attacker calculus
Real Time Response is a cloud-driven remote shell into the sensor: live triage, file pulls, process listings, containment (network isolation with vendor-channel exception), remediation actions. For the defender it collapses triage latency from hours to minutes. For the operator it means the response clock starts at first detection, not first breach — so dwell-time strategy must assume a human analyst is reading your detection within minutes of it surfacing, and can isolate the host with one click while you are mid-operation. Designing for “detect without response” means keeping scores below triage-worthy, which is a much lower bar than “no alert.”
Prevention policies and the maintenance token
Section titled “Prevention policies and the maintenance token”Prevention policies (per platform, per host group) toggle protection categories: next-gen AV, IOA-based prevention, USB device control, sensor tamper protection. Tamper protection + the maintenance token (required for uninstall/manual uninstaller runs [verify token flow details]) make the sensor itself a hardened target — the E2 lesson in vendor form. Sensor update policies control the N-1/N-2 cadence rings — which matters to operators because sensor version differences produce detection-logic differences: a technique that sailed through on an old sensor may block on current, and your test estate’s version skew is an operational bug factory.
Common Techniques
Section titled “Common Techniques”| Falcon surface | What it sees | Operator consideration |
|---|---|---|
| Kernel telemetry (csagent.sys) | Process/thread/image/handle/file events | Sequence design beats tool obfuscation |
| On-sensor IOA/IOC eval | High-confidence behaviors, listed IOCs | Assume instant block for loud chains |
| Cloud IOA/ML | Sub-threshold behaviors, sequences | Assume detection even when no block |
| Threat Graph | Cross-host/cross-time correlation | Infrastructure reuse = persistent signal |
| OverWatch | Human hunters probing anomalies | Novel-but-sloppy tradecraft is their favorite target |
| RTR | Human response minutes after detection | Containment is a click; plan dwell accordingly |
| Uninstall protection | Maintenance token, tamper policy | Never plan around killing the sensor |
Full Working Code
Section titled “Full Working Code”minigraph.py — a working miniature of the Threat Graph idea: ingest Falcon-style event records (ProcessRollup2, network/DNS events — the FDR/Event Streams record names [verify schema field names per API version]), build nodes/edges, run three real correlation queries (Office-spawned lineage, newly-seen domain, cross-host hash prevalence), and print detections. This is the SOC-side view of Falcon and the reason operator infrastructure discipline exists.
{1-6}"""minigraph.py — miniature Threat Graph over Falcon-style events.Ingests FDR-like records (ProcessRollup2, DnsRequest, NetworkConnectIP4),builds a graph, and runs three correlation detections: 1. office-spawned script lineage (T1566.001 -> T1059.001) 2. newly-registered-domain connection (intel join) 3. cross-host hash prevalence (single-sighting binary)Pure stdlib. Python 3.8+."""import jsonfrom collections import defaultdict
NEWLY_REGISTERED = {"cdn-metrics-sync.com"} # intel feed entry (toy)
# ---- record ingestion (FDR-style field names [verify]) -------------def make_proc(pid, ppid, name, cmdline, host, sha256=None): return {"etype": "ProcessRollup2", "pid": pid, "ppid": ppid, "name": name, "cmdline": cmdline, "host": host, "sha256": sha256}
def make_dns(pid, host, domain): return {"etype": "DnsRequest", "pid": pid, "host": host, "domain": domain}
def make_conn(pid, host, domain, ip): return {"etype": "NetworkConnectIP4", "pid": pid, "host": host, "domain": domain, "ip": ip}
class ThreatGraph: def __init__(self): self.procs = {} # (host,pid) -> proc record self.children = defaultdict(list) # (host,ppid) -> [(host,pid)] self.net = [] # dns/conn records self.hash_hosts = defaultdict(set) # sha256 -> {hosts}
def ingest(self, ev): if ev["etype"] == "ProcessRollup2": key = (ev["host"], ev["pid"]) self.procs[key] = ev self.children[(ev["host"], ev["ppid"])].append(key) if ev.get("sha256"): self.hash_hosts[ev["sha256"]].add(ev["host"]) else: self.net.append(ev)
def lineage(self, host, pid): chain, cur = [], (host, pid) while cur in self.procs: chain.append(cur) ppid = self.procs[cur]["ppid"] cur = (host, ppid) if ppid else None return chain
def name_of(self, key): return self.procs[key]["name"].lower() if key in self.procs else ""
# ---- detections ---- def detect_office_script(self): out = [] for (host, pid), ev in self.procs.items(): names = [self.name_of(k) for k in self.lineage(host, pid)] if any("winword" in n or "excel" in n for n in names) and \ ("powershell" in ev["name"].lower() or "wscript" in ev["name"].lower()): out.append(("office-script-lineage", f"{host}: {' > '.join(names)}", "T1566.001 + T1059")) return out
def detect_new_domain(self): out = [] for ev in self.net: dom = ev.get("domain", "") if dom in NEWLY_REGISTERED: out.append(("newly-registered-domain", f"{ev['host']}: {dom}", "T1071.001 + intel")) return out
def detect_hash_prevalence(self): out = [] for sha, hosts in self.hash_hosts.items(): if len(hosts) == 1: out.append(("single-sighting-hash", f"{sha[:16]}... on {list(hosts)[0]}", "prevalence heuristic")) return out
if __name__ == "__main__": g = ThreatGraph() H1, H2 = "FIN-WKS-04", "FIN-WKS-09" # host 1: phishing chain g.ingest(make_proc(1000, 0, "explorer.exe", "", H1)) g.ingest(make_proc(1200, 1000, "WINWORD.EXE", "doc.rtf", H1)) g.ingest(make_proc(1400, 1200, "powershell.exe", "-nop -enc SQBFAFgA", H1, sha256="a" * 64)) g.ingest(make_dns(1400, H1, "cdn-metrics-sync.com")) # host 2: same hash, different story g.ingest(make_proc(2000, 0, "explorer.exe", "", H2)) g.ingest(make_proc(2200, 2000, "excel.exe", "book.xlsm", H2)) g.ingest(make_proc(2400, 2200, "powershell.exe", "-nop -enc SQBFAFgA", H2, sha256="a" * 64)) # SAME binary hash g.ingest(make_conn(2400, H2, "cdn-metrics-sync.com", "203.0.113.7"))
for det in (g.detect_office_script(), g.detect_new_domain(), g.detect_hash_prevalence()): for name, detail, mitre in det: print(f"[{name}] {detail}\n -> {mitre}")Code Walkthrough
Section titled “Code Walkthrough”- Record builders (lines 17–29) — Falcon Event Streams/FDR record shapes, simplified:
ProcessRollup2carries lineage fields (pid/ppid) and binary hash; DNS/connection records join by host+pid [verify exact field names in your API version]. The SOC builds detections from exactly these primitives. ThreatGraph(lines 32–58) — three stores: process records keyed by (host,pid), parent→children edges, and hash→hosts prevalence. The prevalence store is the one operators always forget: a hash sighted once anywhere is asingle-sighting-hashfinding the moment it appears.detect_office_script(lines 62–71) — the phishing-IOA as a lineage query: walk ancestors from each script host; if an Office binary appears, fire. This is the graph join that kills the “boring PowerShell” trick — the ancestry is the signal, not the command line.detect_hash_prevalence(lines 76–80) — deliberately conservative (single-sighting = flag): real programs combine prevalence with intelligence, ML scoring, and analysts. Note host 2 reusing host 1’s hash manufactures the correlation — reuse across hosts is evidence creation.
How It Reaches The Target
Section titled “How It Reaches The Target”Falcon reaches a Windows endpoint as: sensor install (driver at boot, service with tamper protection) → cloud registration + policy pull (prevention/response/sensor-update) → continuous telemetry upload over the management channel → local IOA/IOC evaluation for instant prevention → cloud analytics producing detections → detections triaged by OverWatch/analysts → RTR actions pushed back. For the operator, the practical map: your first stage determines your detection debt — everything it touches (hashes, domains, parent processes) is nodes in someone else’s graph forever. Design consequence: single-use infrastructure, boring lineages, and prevention-policy awareness (a client with aggressive IOA-prevention on script hosts will kill stage one; the same client on “detect-only” policy will record it and let you run — straight into a richer graph).
Exploitation Chain
Section titled “Exploitation Chain”- Pre-op infrastructure — single-use domains, fresh hashes, no recycled certs: minimize what the graph can join tomorrow
- Staging — avoid Office-parented script chains (the classic IOA); compiled droppers or legitimate-publisher delivery reduce lineage signals
- Execution — expect on-sensor IOA eval on injection/memory chains: sequence discipline (E2) keeps sub-prevention scores
- Monitoring — assume detection-without-block is the default state: watch for RTR sessions appearing (your detection surfaced)
- Lateral movement — cross-host joins activate: rotate hashes/parents; never let one host’s story validate another’s
- Exfil — domains tied to staging infra join the graph; single-use everything, again
- Post-op — the graph persists: count on the engagement being reconstructable later; plan disclosure accordingly
Building Your Own Tool
Section titled “Building Your Own Tool”Goal: fdr_explorer.py — a Falcon Data Replicator-style log explorer for SOC learning: loads a JSONL batch of FDR-like records, reconstructs per-host process trees, replays the three minigraph detections, and emits a SIEM-friendly summary. ~130 lines, stdlib, ships with a generator for synthetic batches so you can drill.
Design decisions: (1) JSONL-in / JSON-out — matches how FDR batches actually arrive (flat files of event records [verify packaging]); (2) process-tree rendering as text art (the SOC analyst’s first triage view); (3) pluggable detections — each detection is a function over the graph, mirroring how real pipelines grow rule sets.
{1-3}"""fdr_explorer.py — explore FDR-style event batches: trees + detections.Usage: python fdr_explorer.py --demo # synthetic batch python fdr_explorer.py --file batch.jsonl # your own records python fdr_explorer.py --file b.jsonl --json summary.jsonPure stdlib. Python 3.8+."""import argparseimport jsonimport sysfrom pathlib import Path
from minigraph import ThreatGraph, make_proc, make_dns, make_conn
def demo_batch() -> list: H1, H2 = "HR-WKS-11", "HR-WKS-12" return [ make_proc(500, 0, "explorer.exe", "", H1), make_proc(700, 500, "WINWORD.EXE", "invoice.docm", H1), make_proc(900, 700, "wscript.exe", "invoice.js", H1, sha256="b" * 64), make_dns(900, H1, "cdn-metrics-sync.com"), make_proc(500, 0, "explorer.exe", "", H2), make_proc(750, 500, "chrome.exe", "", H2), make_proc(950, 750, "wscript.exe", "update.js", H2, sha256="b" * 64), # same hash as H1's make_conn(950, H2, "cdn-metrics-sync.com", "203.0.113.7"), ]
def render_tree(g: ThreatGraph, host: str) -> str: lines = [f"host {host}:"] roots = [k for k in g.procs if k[0] == host and (host, g.procs[k]["ppid"]) not in g.procs] def walk(k, depth): ev = g.procs[k] lines.append(" " * depth + f"└─ {ev['name']} (pid={k[1]})") for c in g.children.get(k, []): walk(c, depth + 1) for r in roots: walk(r, 1) return "\n".join(lines)
def run(g: ThreatGraph) -> list: dets = [] for det in (g.detect_office_script(), g.detect_new_domain(), g.detect_hash_prevalence()): dets.extend(det) return dets
def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--file") ap.add_argument("--demo", action="store_true") ap.add_argument("--json", dest="out") args = ap.parse_args()
if args.file: records = [json.loads(l) for l in Path(args.file).read_text().splitlines() if l.strip()] elif args.demo: records = demo_batch() else: print("use --demo or --file"); return 1
g = ThreatGraph() for r in records: g.ingest(r)
hosts = sorted({k[0] for k in g.procs}) for h in hosts: print(render_tree(g, h))
print("\n=== detections ===") dets = run(g) for name, detail, mitre in dets: print(f"[{name}] {detail}\n -> {mitre}") if args.out: Path(args.out).write_text(json.dumps( [{"detection": n, "detail": d, "mitre": m} for n, d, m in dets], indent=2)) return 0
if __name__ == "__main__": sys.exit(main())Walkthrough
Section titled “Walkthrough”demo_batch(lines 16–31) — two hosts telling one story: the samewscripthash on both, one Office-parented, one Chrome-parented. The graph joins them through hash + domain regardless of lineage differences — the operator’s cross-host leak, modeled.render_tree(lines 34–45) — the triage view: roots (processes whose parent is absent) walked depth-first. This is what an analyst sees first; if your lineage looks bad here, everything downstream is confirmation, not discovery.run(lines 48–54) — pluggable detections over one graph object; adding a fourth detection is one function — the architecture that lets SOC rule sets grow without touching ingestion.
Compilation and test procedure
Section titled “Compilation and test procedure”$ python fdr_explorer.py --demohost FIN...: └─ explorer.exe (pid=500) └─ WINWORD.EXE (pid=700) └─ wscript.exe (pid=900)...=== detections ===[office-script-lineage] FIN-WKS-11: explorer.exe > winword.exe > wscript.exe -> T1566.001 + T1059[single-sighting-hash] bbbbbbbbbbbbbbbb... on HR-WKS-11 -> prevalence heuristic$ # your own batch:$ python fdr_explorer.py --file soc_batch.jsonl --json out.jsonThen the SOC drill: export your own EDR-trial’s event stream (or synthesize one), add a fourth detection (e.g., “script host spawned by Office and connecting to non-corporate ASN”), and measure false positives across a day of synthetic noise — the actual daily work of detection engineering against Falcon-shaped data.
Detection considerations
Section titled “Detection considerations”The tool is SOC-side, so its “detection considerations” are about the operator being detected: process-tree shape (Office/script/LOLBin ancestry) is the highest-yield IOA; hash prevalence catches reuse within hours; domain joins catch infrastructure reuse indefinitely. For defenders building on FDR data: normalize timestamps before joins, keep detections pluggable (the run pattern), and remember sensor-version skew — old sensors emit fewer fields, and joins silently fail on missing fields unless validated at ingest.
Improvements to add
Section titled “Improvements to add”- Timestamp ordering and sessionization (events out of order in real batches [verify FDR ordering guarantees]) before lineage walks.
- Add a domain-age intel join (whois-style stub with a local JSON feed) to the new-domain detection.
- Multi-host blast-radius scoring: detections gain weight per additional host joining the same subgraph — the graph’s real power.
- Export Sigma: convert detections to Sigma YAML stubs for the BT3 pipeline.
- Replay mode: ingest a batch, then re-ingest with one field mutated (new hash, new parent) and diff detections — teaches which fields carry the signal.
Detection — Defender View
Section titled “Detection — Defender View”| Source | ID | What fires | Notes |
|---|---|---|---|
| Falcon console | detection IDs | IOA/IOC/ML detections with technique tags | The primary SOC view |
| Falcon Event Streams / FDR | ProcessRollup2, DNS/network records | Raw telemetry for SIEM [verify schema version] | Your own rules live here |
| Falcon RTR | session audit logs | Analyst response actions | Correlate with detection timeline |
| Sysmon (if co-deployed) | 1,3,7,8,10,11,13,22 | Independent verification stream | Cross-vendor joins |
| Windows Security | 7045, 4688 | Service/process events independent of sensor | Court-admissible corroboration |
| July 2024 lesson | channel-file update incident | Content updates can break hosts: stage updates, have rollback plans [verify file naming C-00000291*.sys] | Operational risk management, not detection |
Evasion Techniques
Section titled “Evasion Techniques”- Optimize for the graph, not the gate: single-use infrastructure, fresh hashes per operation, no shared staging — the correlation model punishes reuse more than any single technique.
- Lineage discipline: avoid Office/script-host ancestry for sensitive stages (the top IOA shape); legitimate-publisher parents and compiled droppers starve the classic joins.
- Sub-prevention ≠ sub-detection: a payload that doesn’t block still writes the graph; design the whole operation assuming detection-only mode is recording you.
- Watch for RTR: detection-to-response is minutes on mature SOCs; have the abort criteria and exit plan decided pre-op, not mid-session.
- Sensor-version awareness: test-estate skew matters; a technique validated on N-2 may block on N — check versions as part of pre-op.
- Do not fight the maintenance token: sensor removal is tamper-signaled and token-gated [verify flow]; plan around the sensor, never through it.
⚠️ What caught me: two ops in one quarter reused a staging domain I considered “clean” — different client, different malware, same name in the graph. The second op’s first beacon correlated to the first op’s events and turned a quiet engagement into a client-level incident review. The lesson: Threat Graph doesn’t have a memory limit or a yours/mine boundary where you need one; your infrastructure is a fingerprint you keep pressing onto the glass. Since then: domain per op, hash per op, cert per op — boring, expensive, safe.
Lab Practice
Section titled “Lab Practice”- Run
minigraph.pyandfdr_explorer.py --demo; then mutate the demo batch (same hash on three hosts; Office lineage on one) and observe detection multiplicity — build intuition for what “correlation” costs an operator. - In a Falcon trial (or MDE trial as a stand-in), create two prevention policies (aggressive vs detect-only), run PE8’s injector and a public test payload, and compare block/detection behavior — the prevention/detection split, observed.
- Practice RTR (trial): open a live-response session on a test host, use
cat/ps-class commands, and log the audit trail — know exactly what an analyst can do in the first two minutes. - Build the fourth detection in
fdr_explorer.pyand fuzz it with a day of synthetic noise; report precision/recall — detection engineering as a practiced skill, not a concept. - Case study: read CrowdStrike’s public post-mortem on the July 2024 content update incident; write the one-page risk memo for your own sensor estate (staged rollouts, kill-switch criteria).
Interview Questions
Section titled “Interview Questions”Technical: Contrast Falcon’s IOA and IOC layers, and explain why on-sensor evaluation exists when cloud analytics see the same telemetry. Technical: What is Threat Graph’s node/edge model, and which three correlations make infrastructure reuse the highest-risk operational habit? Deep-dive: Design the detection that catches “single-sighting binary executed by Office lineage connecting to a new domain” as one query over minigraph.py’s stores — then name its false-positive profile. Conceptual: “Prevention policies change what blocks; they never change what’s recorded” — unpack, and give the operational consequences for red and blue teams. Scenario: During an op, an RTR session opens on your host. What has happened upstream, what happens next, and what should your abort plan already contain? Practical: Given a JSONL FDR batch with out-of-order timestamps, write the sessionization step before lineage walks — and explain what breaks without it.
Related Modules
Section titled “Related Modules”E2 (EDR Internal Architecture) · E11 (Defender Detection Logic) · E12 (SentinelOne Detection Logic) · BT9 (EDR Internals)