Impacket is still the Swiss army knife people reach for when they want AD pain without opening Visual Studio. In the lab I care less about the binary name and more about the LDAP questions GetNPUsers and GetUserSPNs ask — because those show up on 389 whether the operator used Impacket, a ripoff script, or something that ships next month with a different filename.
This sits next to the Rubeus post. Same attack ideas, different client, very similar directory filters.
Lab
- Kali (I was on a 2024 image in my notes) + Impacket examples
- Disposable domain / DetectionLab-class DC
- Capture on the DC span or the DC vNIC while you run the tools
- Wireshark filter to start:
tcp.port == 389 and ldap
# AS-REP roast candidates (no preauth)
GetNPUsers.py lab.local/ -dc-ip 10.0.0.10 -usersfile users.txt -format hashcat
# or authenticated enum depending on what you’re doing
GetNPUsers.py 'lab.local/user:password' -dc-ip 10.0.0.10 -request
# Kerberoast via SPN discovery
GetUserSPNs.py 'lab.local/user:password' -dc-ip 10.0.0.10 -request
I’m not going to re-teach the entire attack. If you need the narrative: users without Kerberos preauth, users with SPNs + weak passwords, offline cracking. You already know. Detection is about catching the shopping list.
What GetNPUsers is hunting in LDAP
Conceptually it’s the DONT_REQ_PREAUTH crowd — same UAC bit energy as Rubeus asreproast:
(&
(samAccountType=805306368)
(userAccountControl:1.2.840.113556.1.4.803:=4194304)
)
4194304 = don’t require preauth. If you still have users with that flag in 2024+, that’s a finding even before anyone runs Impacket.
On the wire, Impacket’s BER encoding of that search is what content-match rules try to fingerprint. Open-source reference for the client behavior: Impacket’s GetNPUsers.py on GitHub. That’s the right place to diff versions when your rule dies after an update.
What GetUserSPNs is hunting
Kerberoast targeting — users with SPNs, not disabled, etc.:
(&
(objectCategory=person)
(!(userAccountControl:1.2.840.113556.1.4.803:=2))
(servicePrincipalName=*)
)
(Exact filter strings drift by tool/version — always confirm in your PCAP decode, don’t memorize my blog as gospel.)
Timeline you want in a labeled capture:
- LDAP search with SPN / UAC flavor
- Kerberos TGS-REQ burst for those SPNs (88/tcp)
- Nothing useful for the offline crack — that’s offline on purpose
IDS angle (lab methodology, not a free SIEM pack)
What worked for me in spirit: tcp-pkt / content matches on distinctive BER sequences toward $HOME_NET 389, scoped to DC addresses if you can.
Pattern for AS-REP enum:
- LDAP search request
- UAC bitwise OID +
4194304style payload - objectCategory/computer or user shopping list depending on version
Pattern for SPN enum:
servicePrincipalNameattribute ask near person/user filters- UAC disabled-bit exclude
Example shape only — replace content with bytes from your PCAP:
alert tcp any any -> $DC_SERVERS 389 (
msg:"LAB LDAP ASREP-style UAC 4194304 search";
flow:to_server,established;
content:"|34 31 39 34 33 30 34|"; # ASCII 4194304 — verify in BER context
content:"UserAccountControl"; nocase;
threshold:type limit, track by_src, count 3, seconds 60;
classtype:attempted-recon;
sid:9100201;
rev:1;
)
alert tcp any any -> $DC_SERVERS 389 (
msg:"LAB LDAP SPN enum - servicePrincipalName + person";
flow:to_server,established;
content:"servicePrincipalName"; nocase;
content:"objectCategory"; nocase;
threshold:type limit, track by_src, count 3, seconds 60;
classtype:attempted-recon;
sid:9100202;
rev:1;
)
Those will false positive if you leave them dumb. Identity sync tools, admin scripts, scanners — all love LDAP. Threshold + source allowlists + “not from DCs/identity hosts” is the job.
Also: LDAPS 636 blinds pure NIDS unless you terminate TLS somewhere you can see. Know your estate before you promise wire coverage.
Host / log side (pair it)
- 4769 — TGS requests after GetUserSPNs
-request; watch RC4 (0x17) and high cardinality of services from one user - 4768 / AS-REP paths — for the no-preauth crowd
- Process telemetry on the operator host if you have it (python/impacket) — nice, not sufficient alone
Hunt sketch:
4769 by Account_Name
| uncommon # of distinct ServiceName in 10 minutes
| TicketEncryptionType in (0x17, 0x18)
| Account_Name not in service_account_list
Impacket vs Rubeus — detection sameness
| Impacket | Rubeus | |
|---|---|---|
| Where it runs | Often Linux / python | Often Windows PE |
| LDAP filters | Same ideas | Same ideas |
| Wire story | 389 BER | 389 BER |
| Hashing PE | Weak strategy | Weak strategy |
If your only detection is “Rubeus.exe hash,” Impacket will clown you. Detect the directory question.
Prevention that makes the tools boring
- Find and clear DONT_REQ_PREAUTH users
- No user SPNs with garbage passwords; gMSA where it fits
- AES where you can survive it; stop treating RC4 as normal
- Tier admin so a random workstation can’t LDAP-browse the crown jewels
Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} -Properties DoesNotRequirePreAuth
Test plan (steal it)
- Snapshot lab
- PCAP: GetNPUsers
- PCAP: GetUserSPNs -request
- Confirm LDAP decode + 4769/4768
- Write 1–2 rules from bytes, not from blog posts
- Replay after every sensor upgrade
Related: Rubeus post, BloodHound LDAP.