---
title: "Enumeration Cannot Be Prevented. Ask Whether It Was Seen."
description: "Part two of testing the ransomware playbook: the affiliate's first hour is the same directory collection you run, it cannot be blocked, and the engagement usually destroys the only question worth asking about it on day one."
date: 2025-11-21
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/posts/ransomware-playbook-02-first-hour/
---

## The first authenticated hour in 60 seconds

A credential worked. Something is now authenticated inside the domain, and the next hour is
spent finding out where it landed and what it can reach.

That hour is not exotic either. It is a directory collection — the same one you run, often
with the same tool. Which creates an awkward problem for the report, because directory
enumeration is not a vulnerability. Any authenticated principal can read the directory. That
is not a misconfiguration; it is what a directory is for.

So there is no finding to write about the enumeration itself. The finding is about whether
anybody noticed.

## The question the engagement usually destroys on day one

Almost every internal assessment starts with the tester's source address being allowlisted,
so the SOC does not spend a week chasing an authorised activity. It is a reasonable
operational decision and it is made almost reflexively.

It also throws away the single most valuable measurement available: *would this have been
caught?*

The enumeration is going to happen either way. The affiliate will do it, you will do it, and
neither of you can be stopped from doing it. The only thing that separates the two events is
that one of them gets investigated. Whether that investigation would actually start is a
testable property — and allowlisting is the one decision that guarantees it goes untested.

## Loud and quiet are a choice, and the difference is enormous

Collection is not one behaviour. It is a set of them with wildly different detection
surfaces, and the affiliate picks the same way you do.

<figure class="diagram">
<svg viewBox="0 0 700 256" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Two collection profiles: DC-only collection touches one host and produces LDAP volume alone, while session collection touches every host and produces authentication and share-access events on all of them.">
<text x="0" y="16" class="dg-muted">QUIET — DC ONLY</text>
<rect x="0" y="28" width="118" height="44" rx="8" class="dg-box" />
<text x="14" y="47" class="dg-mono">collector</text>
<text x="14" y="63" class="dg-muted">ONE SESSION</text>
<line x1="120" y1="50" x2="238" y2="50" class="dg-line" />
<rect x="240" y="28" width="118" height="44" rx="8" class="dg-box" />
<text x="254" y="47" class="dg-mono">domain ctrl</text>
<text x="254" y="63" class="dg-muted">LDAP READS</text>
<text x="378" y="44" class="dg-accent">1 HOST TOUCHED</text>
<text x="378" y="62" class="dg-mono">signal is query volume, nothing else</text>
<line x1="0" y1="92" x2="700" y2="92" class="dg-line dg-dash" />
<text x="0" y="118" class="dg-muted">LOUD — SESSION COLLECTION</text>
<rect x="0" y="130" width="118" height="44" rx="8" class="dg-box" />
<text x="14" y="149" class="dg-mono">collector</text>
<text x="14" y="165" class="dg-muted">FANS OUT</text>
<line x1="120" y1="140" x2="238" y2="128" class="dg-line-crit" />
<line x1="120" y1="152" x2="238" y2="152" class="dg-line-crit" />
<line x1="120" y1="164" x2="238" y2="176" class="dg-line-crit" />
<rect x="240" y="114" width="118" height="26" rx="6" class="dg-box-accent" />
<text x="254" y="131" class="dg-mono">host</text>
<rect x="240" y="140" width="118" height="26" rx="6" class="dg-box-accent" />
<text x="254" y="157" class="dg-mono">host</text>
<rect x="240" y="166" width="118" height="26" rx="6" class="dg-box-accent" />
<text x="254" y="183" class="dg-mono">host ×N</text>
<text x="378" y="140" class="dg-crit">EVERY HOST TOUCHED</text>
<text x="378" y="158" class="dg-mono">4624 type 3 and 5145 on each</text>
<text x="378" y="176" class="dg-mono">one source, hundreds of targets, minutes apart</text>
<line x1="0" y1="204" x2="700" y2="204" class="dg-line dg-dash" />
<text x="0" y="228" class="dg-crit">THE POINT</text>
<text x="86" y="228" class="dg-mono">the loud profile is trivially detectable — that is exactly why it is worth</text>
<text x="86" y="246" class="dg-mono">running without an allowlist and seeing whether anything happens</text>
</svg>
<figcaption>Neither profile can be blocked. They differ only in how much evidence they leave, and one of them writes an authentication record on every host in the estate within a few minutes.</figcaption>
</figure>

A DC-only collection is one authenticated session reading LDAP. Everything an attack graph
needs structurally — objects, memberships, ACLs, delegation, trusts — comes from there. It is
quiet because nothing else is touched.

Session collection is the opposite. To learn who is logged in where, the collector has to ask
every host, which means an authentication event and a share access on all of them, from one
source, in a very short window. That shape — one account, hundreds of destinations, minutes
apart — does not occur in normal operation.

Worth connecting to the [earlier note on edge shelf life](/posts/bloodhound-edge-triage/): the
loud half of collection is the half that produces the perishable edges. The affiliate pays a
large detection cost for the data that expires fastest.

## Testing it properly

Run it twice, and treat the difference as the deliverable.

```
# Quiet pass — structural data only, one host touched.
$ bloodhound-python -d lab.local -u svc-audit -p '...' -c DCOnly -ns 10.0.0.10

# Loud pass — session data, fans out across the estate.
$ bloodhound-python -d lab.local -u svc-audit -p '...' -c Session,LoggedOn -ns 10.0.0.10
```

Then ask for the other side of it. Not "did you block this" — nothing blocked it — but what
was recorded and what, if anything, fired:

```
Event 4662  directory object access — the LDAP reads, if object auditing is on at all
Event 4624  logon type 3 — one account, on every host, within minutes
Event 5145  network share object checked — IPC$ on each of those hosts
```

The gap between "these events exist in the log" and "somebody was told" is the entire
finding. In most estates the events are there. Nothing consumes them.

## The cheapest detection nobody has deployed

A canary object costs nothing and catches exactly this behaviour.

Create a user that looks worth having — a plausible name, an SPN, membership that suggests
privilege — and that no legitimate process has any reason to touch. It is not a defence; it
cannot stop anything. It is a tripwire, and its value is that it has essentially no false
positive rate. Nothing in normal operation queries an object nobody uses.

Any collection sweeps it up. Any Kerberoast requests a ticket for it. The alert is
unambiguous in a way that volume-based detection on legitimate directory reads never is.

## Evidence matrix

| Signal | What it proves | Negative control | Defender verification |
| --- | --- | --- | --- |
| DC-only collection completes with LDAP telemetry confined to domain controllers | A quiet graph can be built without touching every endpoint | Compare with session collection from the same host and time window | Baseline query volume and object classes on domain controllers |
| Session collection produces authentication/share events across many hosts | The noisy profile creates a distributed, correlatable footprint | Run DC-only collection and confirm the endpoint fan-out disappears | Correlate one source reaching an unusual percentage of workstations in minutes |
| Canary SPN or directory object is queried during collection | A near-zero-noise tripwire detects enumeration intent | Confirm ordinary administrative workflows never touch the canary | Alert on reads or ticket requests involving the canary and periodically test delivery |
| Collection from a standard-user foothold yields a usable path | The exposure is reachable from a plausible first-hour position | Run the same query from a deliberately restricted segment/account | Validate directory read restrictions, network tiering, and telemetry from the starting zone |

## The pattern I keep seeing

Directory enumeration sits in a gap between two teams. It is not a vulnerability, so it does
not appear in a pentest report. It is not malware, so it does not trigger the endpoint
product. The telemetry exists and goes to a platform where nobody has written a rule for it,
because writing that rule requires knowing what normal directory access looks like in that
specific estate — and that is a project, not a ticket.

So it stays unwatched, and the loudest hour of the entire intrusion passes unremarked.

## What to hand the defenders

**Push back on the allowlist, at least once.** Ask for one unannounced collection window
before the address goes on the list. It costs the SOC an hour and it answers a question no
other part of the engagement will.

**Report the detection gap as its own finding.** "Directory enumeration generated 4624 and
5145 on every host in the estate and produced no alert" is a finding. It has a specific,
achievable remediation, and it is more actionable than most of the vulnerabilities in the
same report.

**Recommend the canary.** It is a single object, it needs no product, and it converts an
undetectable-by-default behaviour into a high-confidence alert.

The affiliate cannot be stopped from mapping the domain. They can be watched doing it, and
the difference between those two facts is most of the response timeline.

---

*Next in this series: privilege escalation — the specific paths that get used, and why they
are the ones already written up in this blog.*