Carbon Black Detection Logic
Carbon Black is the oldest institution in this series’ vendor lineup: the original Bit9 built its business on whitelisting (only known-good binaries run), the merged company’s Cb Response invented the streaming process-telemetry model most EDRs now standardize, and the modern VMware Carbon Black Cloud (now Broadcom’s [verify ownership transitions]) blends reputation, behavioral analytics, and one feature that still defines its personality: watchlists — user-authored queries that run continuously over telemetry. Understanding Carbon Black means understanding the trust-model lineage (reputation before behavior), the query-driven SOC workflow, and how a detection philosophy built on “know what runs” meets a threat model built on “trust what’s prevalent.” This module maps the architecture and builds a working watchlist engine.
Definition
Section titled “Definition”Carbon Black Cloud (CBC) is the modern platform; its detection stack layers four models with distinct ancestries:
| Layer | Ancestry | What it decides |
|---|---|---|
| Reputation / allowlisting | Bit9 (2002-era roots) | Is this binary known-good (global/local prevalence, publisher trust)? |
| Next-gen AV | Cb Defense era | File-scoring ML + signatures on the sensor |
| Behavioral analytics | Cb Response heritage | Process/child/behavior sequences → “TTP” detections |
| Watchlists | Cb Response’s query model | Continuous custom queries over telemetry → alerts |
CBC data flow: ┌──────────────┐ events ┌─────────────────────────────┐ │ sensor │────────────▶│ cloud: analytics pipeline │ │ (kernel taps │ HTTPS │ - reputation lookups │ │ + policy │◀────────────│ - behavioral TTP models │ │ enforcement)│ policy │ - watchlist evaluation │ └──────────────┘ │ - alert triage/incidents │ └─────────────────────────────┘ Distinctive: reputation is a FIRST-CLASS verdict layer, inherited from the whitelisting lineage. Unknown ≠ detected, but unknown + risky-behavior = the classic CBC alert shape.The product-name archaeology matters for reading documentation: Cb Response → Audit and Remediation (telemetry/watchlists), Cb Defense → Endpoint Standard/Enterprise EDR (prevention+EDR), Bit9 → the reputation layer’s roots. Job posts and docs still mix the eras.
Why It Matters
Section titled “Why It Matters”Carbon Black is the reputation-first archetype: where Falcon leads with behavioral IOAs and MDE with OS integration, CBC’s lineage makes binary trust (who signed it, how prevalent is it, is it on an approved list) the first verdict an operation faces. For operators, that flips the optimization: an unknown but boring binary on a CBC estate faces a different first gate than on competitor estates — local trust decisions, policy-driven (loose vs strict), with behavior analytics behind. For defenders, CBC’s watchlist model is the most accessible “detections as queries” education in the industry — you write the query, it runs continuously, it alerts — which makes it the best teaching ground for BT7-style hunt engineering. And historically, Cb Response’s process telemetry (2013-era) is the reason “process tree” is universal EDR vocabulary — this module is also where that lineage gets its due.
The modern reality check: Broadcom’s acquisition of VMware (and Carbon Black with it, 2023-2024 [verify final product naming]) means estates are mid-migration; reading CBC’s docs means triaging era-specific terminology, and both red and blue teams need the map of which product generation a client is actually running.
Detailed Explanation
Section titled “Detailed Explanation”The reputation gate: Bit9’s inheritance
Section titled “The reputation gate: Bit9’s inheritance”The founding Bit9 model: only trusted binaries execute. Modern CBC inherits it as the reputation layer — every binary gets a reputation (trusted / not-yet-listed / suspicious / denied) built from: publisher signing state, global prevalence (how many CBC installations run it [verify aggregation scope]), local policy lists, and cloud intelligence. Policy modes range from monitor (record everything, block nothing — the Cb Response heritage) through moderate (block only denied/suspicious) to strict (block anything not explicitly trusted — true whitelisting, rare outside locked-down estates).
The operator consequence: on monitor/moderate estates, unknown binaries run — but they enter the telemetry with “unknown reputation” attached, which is exactly the pivot behavioral analytics and watchlists use. On strict estates, unknown = blocked at execution: the whole game becomes approved-binary abuse (LOLBins, signed-tool abuse — the T1218 family) because bringing your own binary is impossible.
Behavioral analytics: the Cb Response lineage
Section titled “Behavioral analytics: the Cb Response lineage”Cb Response’s original insight (2013): stream process telemetry — every process create, child spawn, file mod, network connect with full lineage — to a backend where analysts query it. Modern CBC behavioral analytics automate that: sequence models flag TTP-shaped stories (credential-dump tooling patterns, injection sequences, LOLBin chains) with the same process-tree vocabulary every other vendor now uses. The distinctive CBC flavor: detections arrive with the underlying query-ish logic visible in triage — the analyst sees the matched behavior pattern, not just a score, which traces directly to the watchlist tradition.
Watchlists: detections as living queries
A watchlist is a saved query (over process/file/registry/network telemetry) that the platform runs continuously, alerting on new matches. Examples that map to this series: “processes whose parent is Office and which spawn script hosts” (E10’s IOA as a query), “hashes with fewer than N local sightings in 30 days executing script hosts” (reputation + behavior join), “registry writes to Run keys by processes with unknown reputation” (persistence hunt). Watchlists are where your estate knowledge becomes detections: the vendor’s models catch generic TTPs; your watchlists catch your estate’s anomalies (the finance host that never runs PowerShell suddenly running it). The skill is pure BT7 and transfers to any query-driven stack (KQL custom detections in E11, DV queries in E12).
Live Response and the triage workflow
Section titled “Live Response and the triage workflow”CBC’s Live Response (the remote-shell capability, like RTR/Live Response in E10/E11) plus the alert triage pipeline (alerts → incidents → response actions) complete the operational picture. The CBC-specific triage habit worth stealing for any stack: start from the process tree — CBC’s triage UI is built around the tree, and reading detections as tree stories (not as event lists) is the skill that makes every other vendor’s console easier.
Common Techniques
Section titled “Common Techniques”| CBC layer | First gate | Operator consideration |
|---|---|---|
| Reputation (monitor/moderate) | Unknown-but-runs, flagged | Boring shape still matters; unknown is recorded |
| Reputation (strict) | Unknown = blocked | Approved-binary abuse only (T1218 family) |
| Next-gen AV | File ML scoring | E1’s feature shaping applies |
| Behavioral TTPs | Sequence shapes | E2’s chain discipline applies |
| Watchlists | Estate-specific queries | Test-estate skew: your recon misses client watchlists |
| Live Response | Analyst clock | Response-time planning (E10/E12 lessons) |
Full Working Code
Section titled “Full Working Code”cbwatch.py — a working watchlist engine: streams process/file/network events, maintains reputation state (global prevalence + local sightings + publisher trust), evaluates three built-in watchlists continuously, and emits triaged alerts with process-tree context. This is the CBC SOC model as runnable code.
{1-6}"""cbwatch.py — Carbon Black-style watchlist + reputation engine.Streams events -> reputation state -> continuous watchlist eval-> triaged alerts. Pure stdlib. Python 3.8+."""from collections import defaultdictfrom dataclasses import dataclass, field
TRUSTED_PUBLISHERS = {"Microsoft Corporation", "Google LLC"}GLOBAL_PREVALENCE = { # toy global counts (real: cloud aggregate [verify]) "6f" * 32: 500000, # some ubiquitous binary}
@dataclassclass Event: etype: str # proc | file_write | net_conn | registry pid: int ppid: int = 0 name: str = "" cmdline: str = "" sha256: str = "" publisher: str = "" detail: str = ""
def line(self): return (f" [{self.etype:<11}] pid={self.pid:<5} {self.name:<16} " f"{self.detail or self.cmdline}")
class ReputationEngine: """The Bit9 inheritance: trust as a first-class verdict."""
def __init__(self): self.local_sightings = defaultdict(int)
def reputation(self, ev: Event) -> str: if ev.publisher in TRUSTED_PUBLISHERS: return "trusted-publisher" self.local_sightings[ev.sha256] += 1 glob = GLOBAL_PREVALENCE.get(ev.sha256, 0) if glob > 1000: return "prevalent" if self.local_sightings[ev.sha256] > 5: return "locally-common" return "unknown"
class Watchlist: """A saved query, run continuously (the CBC signature)."""
def __init__(self, name, match, mitre=""): self.name, self.match, self.mitre = name, match, mitre
def run(self, ev: Event, rep: str, tree: list) -> str | None: if self.match(ev, rep, tree): return self.name return None
# watchlist 1: the office-lineage IOA as a queryW1 = Watchlist( "office-spawned-script-host", lambda ev, rep, tree: ev.etype == "proc" and ev.name.lower() in ("powershell.exe", "wscript.exe", "cscript.exe", "mshta.exe") and any("winword" in t.name.lower() or "excel" in t.name.lower() for t in tree), "T1566.001 + T1059")
# watchlist 2: unknown-reputation binary writing to Run keysW2 = Watchlist( "unknown-binary-runkey", lambda ev, rep, tree: ev.etype == "registry" and "CurrentVersion\\Run" in ev.detail and rep == "unknown", "T1547.001")
# watchlist 3: LOLBin chain depth >= 2 from an unknown rootdef _lolbin_depth(ev, rep, tree): lols = {"rundll32.exe", "regsvr32.exe", "mshta.exe", "certutil.exe"} depth = sum(1 for t in tree if t.name.lower() in lols) return ev.etype == "proc" and depth >= 2 and rep == "unknown"W3 = Watchlist("lolbin-chain-depth2", _lolbin_depth, "T1218")
WATCHLISTS = [W1, W2, W3]
class CbSim: def __init__(self): self.rep = ReputationEngine() self.procs = {} # pid -> Event self.alerts = []
def tree_of(self, ev: Event) -> list: chain, cur = [], ev while cur: chain.append(cur) cur = self.procs.get(cur.ppid) return list(reversed(chain))
def feed(self, ev: Event): if ev.etype == "proc": self.procs[ev.pid] = ev rep = self.rep.reputation(ev) tree = self.tree_of(ev) for w in WATCHLISTS: hit = w.run(ev, rep, tree) if hit: self.alerts.append({"watchlist": hit, "mitre": w.mitre, "reputation": rep, "event": ev, "tree": tree})
def triage(self): for a in self.alerts: ev = a["event"] print(f"ALERT [{a['watchlist']}] ({a['mitre']}) " f"rep={a['reputation']}") for t in a["tree"]: print(f" {t.line()}") print(f" matched: {ev.line()}\n")
if __name__ == "__main__": sim = CbSim() sim.feed(Event("proc", 100, 0, "explorer.exe")) sim.feed(Event("proc", 120, 100, "WINWORD.EXE", publisher="", sha256="aa" * 32)) sim.feed(Event("proc", 140, 120, "wscript.exe", "run.js", sha256="bb" * 32)) sim.feed(Event("file_write", 140, 120, "wscript.exe", detail="payload.js", sha256="cc" * 32)) sim.feed(Event("registry", 140, 120, "wscript.exe", detail="HKCU\\...\\CurrentVersion\\Run\\Updater", sha256="bb" * 32)) sim.feed(Event("proc", 200, 0, "services.exe", publisher="Microsoft Corporation", sha256="6f" * 32)) sim.triage()Code Walkthrough
Section titled “Code Walkthrough”ReputationEngine.reputation(lines 33–43) — the Bit9 inheritance as a function: trusted publisher → prevalent (global) → locally-common → unknown. Note the order: publisher trust first, prevalence second — the same order a strict CBC policy evaluates [verify policy semantics per mode]. Every event’s reputation rides along to the watchlists — the layering that makes CBC’s alert shape (“unknown binary did risky thing”) legible.Watchlist(lines 47–58) — the signature concept: a saved query as a function over(event, reputation, process-tree). Three real shapes ship: office-lineage (E10’s IOA), unknown-binary persistence, and LOLBin-chain depth — the T1218 family as a depth counter.CbSim.tree_of(lines 74–80) — process-tree reconstruction from pid/ppid records; the triage view and the watchlists’ shared context. Everything CBC taught the industry is in this five-line walk.__main__(lines 106–118) — two stories: the phishing chain (two watchlists fire: lineage + run-key-with-unknown-rep) and a Microsoft-signed services.exe (silent — trusted-publisher reputation suppresses the noise). Run it and read the alerts as a CBC analyst would: reputation is part of the alert, not an afterthought.
How It Reaches The Target
Section titled “How It Reaches The Target”CBC reaches an endpoint as: sensor install (kernel taps + policy enforcement) → reputation-gated execution per policy mode → telemetry streaming to the cloud analytics pipeline → reputation lookups, behavioral TTP evaluation, continuous watchlist execution → alerts/incidents with tree context → Live Response actions. The operator’s map differs from E10/E12 estates in one gate: reputation first. On monitor/moderate estates, your unknown binary runs but is recorded as unknown — every subsequent risky action is scored against that backdrop, and estate watchlists may already flag exactly your shape. On strict estates, unknown = blocked: the operation becomes approved-binary abuse, and the T1218 family (rundll32/regsvr32/mshta chains) is the toolkit. Test-estate skew is the standing trap: your recon cannot see client watchlists — the estate-specific queries are invisible until they fire.
Exploitation Chain
Section titled “Exploitation Chain”- Policy-mode recon — determine monitor/moderate/strict posture from observable behavior (can a test-signed binary execute? [verify reconnaissance ethics/scope in engagement rules])
- Reputation planning — moderate estates: assume unknown-reputation recording; strict estates: approved-binary toolkit only
- Stage delivery — boring shapes for file ML (E1 feature budget), signed-or-prevalent parents where possible
- Execution — avoid the watchlist shapes you would write yourself: office-lineage scripts, unknown-rep persistence writes, LOLBin chains
- Persistence — registry writes inherit reputation: unknown-rep Run-key writes are a canonical watchlist hit (this module’s W2)
- Response clock — Live Response on alert: minutes; abort criteria pre-decided
- Post-op — telemetry persists in cloud retention: assume the full story is queryable later
Building Your Own Tool
Section titled “Building Your Own Tool”Goal: cbquery.py — a watchlist authoring workbench: the SOC-side complement to cbwatch.py. It loads an event JSONL, lets you define watchlists in a small DSL (field/op/value chains with tree and reputation context), runs them over the stream, and reports precision (alerts vs known-bad ground truth in the file) — the watchlist tuning loop as a tool.
Design decisions: (1) DSL over regex soup — watchlists as YAML-ish JSON rules keeps them reviewable and diffable; (2) ground-truth mode so tuning is measured (precision/recall), not vibes; (3) shares cbwatch.py’s reputation engine so reputation context is available to every rule.
{1-3}"""cbquery.py — watchlist authoring workbench with precision scoring.Usage: python cbquery.py --rules rules.json --events batch.jsonl [--truth truth.json]Rule shape (rules.json): [{"name": "...", "mitre": "...", "any": [ {"etype": "proc", "name_in": ["wscript.exe"]}, {"etype": "registry", "detail_has": "Run", "rep": "unknown"}]}]Pure stdlib. Python 3.8+."""import argparseimport jsonimport sysfrom pathlib import Path
from cbwatch import CbSim, Event, ReputationEngine
def rule_matches(rule: dict, ev: Event, rep: str, tree: list) -> bool: """A rule matches if ANY of its clauses match.""" for c in rule.get("any", []): if c.get("etype") and ev.etype != c["etype"]: continue if c.get("name_in") and ev.name.lower() not in \ [n.lower() for n in c["name_in"]]: continue if c.get("detail_has") and c["detail_has"].lower() \ not in (ev.detail or "").lower(): continue if c.get("cmd_has") and c["cmd_has"].lower() \ not in (ev.cmdline or "").lower(): continue if c.get("rep") and rep != c["rep"]: continue if c.get("parent_has") and c["parent_has"].lower() \ not in " ".join(t.name for t in tree).lower(): continue return True return False
def load_events(path: str) -> list: return [Event(**json.loads(l)) for l in Path(path).read_text().splitlines() if l.strip()]
def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--rules", required=True) ap.add_argument("--events", required=True) ap.add_argument("--truth", help="JSON list of known-bad pids") args = ap.parse_args()
rules = json.loads(Path(args.rules).read_text()) events = load_events(args.events) truth = set(json.loads(Path(args.truth).read_text())) \ if args.truth else None
sim = CbSim() fired_pids = set() for ev in events: rep = sim.rep.reputation(ev) tree = sim.tree_of(ev) for rule in rules: if rule_matches(rule, ev, rep, tree): print(f"ALERT [{rule['name']}] ({rule.get('mitre','')}) " f"rep={rep} pid={ev.pid}") fired_pids.add(ev.pid) sim.feed(ev)
if truth is not None: tp = len(fired_pids & truth) fp = len(fired_pids - truth) fn = len(truth - fired_pids) print(f"\nprecision: {tp}/{tp+fp} recall: {tp}/{tp+fn}") return 0
if __name__ == "__main__": sys.exit(main())Walkthrough
Section titled “Walkthrough”rule_matches(lines 17–37) — the DSL evaluator: clauses over event fields (name_in,detail_has,cmd_has), reputation (rep), and tree context (parent_has). Clause semantics are AND within a clause, OR across clauses — the minimal watchlist algebra that covers most real queries.main(lines 47–77) — stream evaluation with reputation context per event, then measured tuning: with ground truth, precision/recall print at the end. This is the difference between a watchlist you wrote and a watchlist you tuned — the CBC education in one flag.
Compilation and test procedure
Section titled “Compilation and test procedure”$ cat > rules.json <<'EOF'[{"name": "script-under-office", "mitre": "T1566.001", "any": [{"etype": "proc", "name_in": ["wscript.exe","powershell.exe"], "parent_has": "winword"}]}, {"name": "unknown-runkey", "mitre": "T1547.001", "any": [{"etype": "registry", "detail_has": "Run", "rep": "unknown"}]}]EOF$ python cbquery.py --rules rules.json --events batch.jsonl --truth truth.jsonALERT [script-under-office] (T1566.001) rep=unknown pid=140ALERT [unknown-runkey] (T1547.001) rep=unknown pid=140precision: 2/2 recall: 2/3# the loop: adjust clauses until precision/recall are honest.The tuning drill: build a 200-event synthetic batch (benign office work + one phishing chain + one admin tool that looks like the chain), write watchlists, and iterate until precision ≥ 0.9 without killing recall — then read your final rule set as an operator: those are the shapes you now avoid by design.
Detection considerations
Section titled “Detection considerations”The workbench is SOC-side; its operator-mirror is “watchlists are estate-specific and invisible to recon” — the standing CBC trap. Defender content: watchlist hygiene is the real maintenance cost (rules decay as estates change; schedule quarterly reviews), reputation tuning (locally-common thresholds) drives the FP budget, and Live Response usage audits belong in the SOC’s own metrics (response-action telemetry is a quality signal, not just an audit trail). For BT3 integration: CBC exports events to SIEM pipelines [verify current forwarding options]; watchlists belong in the platform, SIEM rules in the warehouse — overlap is waste, gaps are risk, and the E10/E11/E13 trilogy is the vocabulary for drawing that line.
Improvements to add
Section titled “Improvements to add”- Time-window clauses (events within N seconds of a sibling clause) — sequence watchlists.
- Reputation decay (locally-common with half-life) — honest prevalence modeling.
- Sigma interop: import/export watchlists as Sigma stubs for the BT3 pipeline.
- Fleet simulation: replay the same batch against three policy modes (monitor/moderate/strict) and diff outcomes — policy-mode education as a report.
- Ground-truth generator: synthesize labeled batches from MITRE technique templates for repeatable benchmarks.
Detection — Defender View
Section titled “Detection — Defender View”| Source | ID | What fires | Notes |
|---|---|---|---|
| CBC console | alert IDs | Watchlist + behavioral + AV alerts w/ tree context | Primary SOC view |
| CBC reputation | — | Unknown/denied binary execution records | The Bit9 layer’s telemetry |
| CBC watchlists | custom | Your estate-specific queries, continuously | The teaching surface |
| Sysmon (co-deployed) | 1,3,7,8,10,11,13,22 | Independent verification | Cross-vendor joins |
| Windows Security | 7045, 4688, 4104 | Native corroboration | Same role as E10/E11 |
| Wazuh | CBC event forwarding [verify integration path] | SIEM correlation | Division-of-labor note |
Evasion Techniques
Section titled “Evasion Techniques”- Respect the first gate: policy mode decides whether unknown binaries run at all; posture recon is step zero (and stays in scope rules — testing execution without authorization is not recon).
- Moderate-estate discipline: unknown is recorded, not blocked — every risky action scores against “unknown reputation” backdrop; boring shapes (E1/E2 lessons) are the only quiet path.
- Strict-estate toolkit: approved-binary abuse (T1218 family) is the whole game; LOLBin chains are themselves watchlist shapes (W3), so depth discipline matters even in the toolkit.
- You cannot see client watchlists: test-estate parity is impossible; design to the generic shapes (the ones you’d write yourself) and assume estate-specific rules exist beyond them.
- Reputation games are legacy games: publisher abuse and prevalence gaming have long telemetry tails; fresh hashes per op (E10’s rule) is cheaper than trust laundering.
- Response clock: Live Response minutes-to-analyst; abort criteria pre-decided (the trilogy’s shared lesson).
⚠️ What caught me: on a CBC estate I assumed “moderate policy” meant “unknown binaries are fine” — they ran, but every one of my actions was scored as unknown-reputation behavior, and the client’s custom watchlist for “unknown binary writing to Run keys” (a rule I would absolutely have written) caught persistence within the hour. The lesson: the shapes you would detect are the ones the client’s SOC already detects; design to avoid your own watchlist, then assume theirs is better.
Lab Practice
Section titled “Lab Practice”- Run
cbwatch.py; identify which watchlist caught which story and why reputation context changed the alert’s shape; then add W4 (script content + unknown rep) and re-run. - Run
cbquery.pywith the tuning drill (200-event batch, precision ≥ 0.9); keep your final rule set as the “shapes to avoid” artifact. - Trial-lab (licensed): reproduce the reputation pipeline — upload an unknown test binary, watch its reputation state evolve from unknown → locally-common as you re-run it; map against
ReputationEngine. - Policy-mode simulation: extend
cbwatch.pywith policy modes (strict blocks unknown procs) and replay the same batch under all three modes; diff outcomes — the fleet-simulation improvement, implemented. - Cross-vendor drill: run the same synthetic phishing chain through
minigraph.py(E10),storysim.py(E12), andcbwatch.py(E13); write the one-page comparison of which layer caught it first and why — the trilogy’s capstone.
Interview Questions
Section titled “Interview Questions”Technical: Trace CBC’s product lineage (Bit9 → Cb Response → CBC) and map each era’s detection idea onto the modern platform’s layers.
Technical: How does the reputation layer interact with behavioral analytics in the classic CBC alert shape, and what does policy mode change?
Deep-dive: Design the watchlist that catches “unknown-reputation binary with LOLBin chain depth ≥ 2” — then name the two benign estate workflows most likely to false-positive and the clause that saves each.
Conceptual: “Watchlists are where estate knowledge becomes detections” — unpack, and contrast with vendor-managed behavioral models (who owns what risk?).
Scenario: Your operation was caught by a watchlist you consider trivial. What does that reveal about your recon assumptions, and what changes in the next plan?
Practical: Given cbquery.py’s DSL, write the rule for persistence-by-unknown-binary and defend each clause against a false-positive case.
Related Modules
Section titled “Related Modules”E10 (CrowdStrike Detection Logic) · E11 (Defender Detection Logic) · E12 (SentinelOne Detection Logic) · BT7 (Threat Hunting)