A few months ago I started what I thought was fairly straight forward piece of work: rewrite/fork Impacket internally for use at my current company. What I didn’t expect was that the project would end up dragging me into the deep end of Kerberos, NTLM, SPNEGO, SMB2/3, DCE/RPC and DCOM in a way that has changed the way I view offensive tooling, my mindset as an operator and finally my skillsets as a operator capable of emulating the stealthiest of adversaries.
That internal project is what eventually became Impacket-IoCs, a public reference of 72+ (and counting) protocol-level and implementation-level indicators of compromise for detecting Impacket-driven activity. This blog post is the long-form version of why it exists, how I went about it, and some of the things I learned along the way that I genuinely didn’t expect to learn.
How this even started
Once you start peeling off the onion-like layers of the toolkits you supposedly know, you start noticing things. You start asking why is this field set this way, and is this how Windows actually does it?
So I went looking for prior work that could help me with my re-write. Surely, I figured, the most abused offensive toolkit on the planet(toolkit so excluding mimikatz, sorry benji xD), the one that practically every IR report from the last decade name-drops, has at least some public protocol-level detection research behind it. Right? Right…?
It does not. Or at least, not in any meaningful way that a blue teamer without a six-figure detection subscription can actually use. Most of what I found was filename-based, command-line-based, or surface-artifact-based. Service named BTOBTO. Binary dropped to C:\Windows\Temp. Output redirected to __output. Potentially Useful, but trivial for an operator to change in less than 2 minutes of effort.
What made it worse was checking what some of the bigger vendor products were actually firing on IoC wise. I’m talking about companies with valuations in the billions! String matches on default service names or files created with unix epoch time as its name(such as what dcomexec.py does) Stuff that you expect from a github repo as opposed to a EDR/Detection rule stack that is costing you 100s of thousands a year in fees.
At that point I sort of accepted I was going to have to go at it alone. Which, was fine for me. Ultimately, that’s how most of the more interesting projects start : )
The approach
I set up a Windows Server 2022 system from the sccmlab made by @an0n_r0 on X and used pktmon to capture everything across the relevant ports for Windows authentication:
pktmon filter remove
:: Core auth
pktmon filter add Kerberos -p 88
pktmon filter add LDAP -p 389
pktmon filter add LDAPS -p 636
pktmon filter add GC -p 3268
pktmon filter add GCS -p 3269
pktmon filter add SMB -p 445
pktmon filter add RPC-EPM -p 135
pktmon filter add DNS -p 53
:: WinRM / PowerShell remoting
pktmon filter add WinRM -p 5985
pktmon filter add WinRMS -p 5986
:: Web / Windows Integrated Auth
pktmon filter add HTTP -p 80
pktmon filter add HTTPS -p 443
:: RDP (CredSSP / NLA)
pktmon filter add RDP -p 3389
:: NetBIOS legacy
pktmon filter add NetBIOS-NS -p 137
pktmon filter add NetBIOS-SS -p 139
pktmon start -c --pkt-size 0 -f C:\auth.etl --comp nics
:: ... do your Windows auth actions ...
pktmon stop
pktmon etl2pcap C:\auth.etl --out C:\auth.pcapng
From there it was on to Wireshark. Two configuration steps were needed to be done to begin observing the appropriate traffic: drop the user’s password into the NTLMSSP protocol settings, and build a keytab containing the krbtgt AES256 key, the domain controller machine account’s AES256 key, and the Domain Controllers machine account’s RC4 key. The keytab is what lets you decrypt the sections of Kerberos exchanges encrypted under the target machine’s key rather than only the bits encrypted by krbtgt. The target in my case for this project was the domain controller.
Then the next step was preforming as many actions as I could to absorb the widest range of authentication packets. I’d run a reg query against the domain controller using its hostname, then klist purge the tickets and run it again using the IP address to force NTLM. Same steps taken again for WMI/DCOM with Get-WmiObject -ComputerName against both DNS name and IP. SMB activity was triggered with dir against the DCs file shares as well as the Users folder(Using C$\Users). The hardest was LDAP, which I triggered with gpupdate /force and a handful of ADSI searches and anything else I found to trigger LDAP binds/authentication. Every time I executed a command, I’d purge tickets to force fresh ones. The goal was to soak up as much normal Windows traffic across as many authentication scenarios as possible to begin sifting through and understanding what Windows was doing.
Then I tried my very best with the same activity, same actions, same target(the DC), but generated through Impacket. wmiquery.py and reg.py for DCE/RPC. smbclient.py for SMB. getADUsers.py, getADComputers.py as well as using getST.py to get service tickets and use them with netexec, for LDAP authentication telemetry. From there it was diff diff diff till your hearts content.
That’s the whole methodology, really. As you may have figured out, the biggest skillset required thus far is just patience and the willingness to read RFCs and Microsoft’s [MS-*] specifications until your eyes bleed.
The findings that surprised me
Very quickly my findings split roughly into two camps, and the contrast between them is probably the biggest lesson born out of this project.
The first camp was the obvious stuff. The kind of thing where you look at Impacket and you go huh, that’s either oversight or a honeypot. The SMB2/3 ClientGuid and pre-authentication hash being ASCII letters instead of an actual GUID across the full byte range. NTLMv2 challenge fields holding ASCII rather than the structured bytes Windows uses. Hardcoded nonce values in ticketer.py. DCE/RPC-generated GUIDs that didn’t follow RFC 4122 conformance with wrong version nibble, wrong variant bits (Microsoft and basically everyone else uses version 4, variant 1, so you’ll see the third group start with a 4 and the fourth group start with 8, 9, a, or b. Impacket’s didn’t.). Others in this class included the seq-number added into Kerberos authentication(AS/AP exchanges) where Impacket would set it to either 0 or nothing at all. These are findings that an operator could fix relatively easily once they were aware of them. Some or most are just one-line changes. Also its concerning that security vendors didn’t have detections/tags for any of these.
The second camp is where the project genuinely changed my mindset, and uplifted my skillsets. These were findings that came from sitting in protocol specifications long enough to start seeing them, and they ranged from interesting to genuinely mind blowing depending on how core they were to the protocol.
The biggest oh wow moment, was the SPNEGO mechToken field carrying a raw Kerberos AP-REQ directly in LDAP and MSSQL/TDS login paths. I lost two full days to this one as wireshark’s dissector generated a parse error that, in hindsight, was a red herring of epic proportions. I bounced between MS-KILE, RFC 4120, and the Wireshark source code, convinced the issue/mistake was from some step I took at some stage; this almost drove me mad. Evenetually, and by complete chance I noticed the first two bytes were 0x6E rather than the 0x60 I was expecting. A quick trip back to the ASN.1 definitions for application class tags and the whole picture clicked: 0x60 is [APPLICATION 0], the GSS-API generic token wrapper. 0x6E is [APPLICATION 14], which is the raw KRB_AP_REQ PDU.
Impacket was just… shoving the AP-REQ in there directly, skipping the GSS-API wrapping that a legitimate Windows client would always perform. Once you see it, you cannot unsee it. And once you know to look for 0x6E in that field, you’ve got a fingerprint that no operator is patching out with their favorite frontier model. Interestingly enough though, there were other code pathways that did correctly frame this such as with SMB2/3 as well as DCE/RPC. Looking at the commits, it does seem that the few years gap between certain code changes/additions likely mean someone at some stage wised up and added the proper framing.
There were others in that second camp. The DCE/RPC invocation of ISystemActivator for DCOM activation using local-machine access notation with forward slashes (//./root/cimv2) where the spec quite clearly mandates backslashes, and where Windows clients also explicitly clarify the target as a remote resource rather than a local one. The auth_context_id arithmetic of 79231 + ctx_id added to the Impacket fingerprint. DCE/RPC authentication padding using 0xff rather than the values Windows uses. WMI’s IWbemLevel1Login::NTLMLogin setting things like the preferred Locale to null while the specification states that the setting of null should only be done after the clients and server call the EstablishPosition method and determine that they don’t share a supported/desired language locale. Other honorable mentions in this class include LDAP Sicily Bind using the CIFS service name in the relevant target AV Pair as opposed to the expected/real service… which is LDAP : D
Very important note: These weren’t oversights that are a bad reflection on anyone who developed the library. They are things that happen when you implement a protocol to make it work rather than to make it indistinguishable from the canonical implementation. This is expected in a lot of places, but Impacket, either due to its size or other factor; has way too many deviations that make it very easy to pick up if you know what to look for. In a lot of places in the code base, Impacket does not shy away from saying that the implementations are either minimal, unstable, partly done or still have work to do, Think the ntlm class in ntlm.py not having a completed method to check the presence of a MIC in an NTLM message, which has a associated Todo tag on it ; )
The findings span that whole spectrum. Some I’d expect any average joe to be able to rectify, and clean out of the project(like the SMB2/3 clientGUID). But the crown jewels, are the ones that require a meaningful rethink of how a given protocol is structured/implemented. And that’s the gap where durable detection lives. Thats also the gap where red teamers have to spend the biggest time exploring and tearing apart to get it right.
Some thoughts I’ve been sitting with
A couple of things shifted in my head over the course of doing this.
The first is that the asymmetry between offensive tool maturity and the public detection knowledge surrounding those tools is genuinely worse than I expected. Impacket has been around since basically forever in security-tool years. It is not obscure. It is not exotic. It’s the toolkit. And yet the public well of protocol-level detection content for it is non existent.
The second is something I think red teamers in particular don’t think about often enough: your toolkits are shaping traffic in ways you may have never personally inspected. You can rename services, change output paths, recompile with different defaults, run everything through network redirector, and the library underneath is still misbehaving when it sends across a SPNEGO token that starts with 0x6E instead of 0x60 or its TGS-REQ is missing the pA-data type PA-PAC-Options with the expected bits set. That’s the thing. The OPSEC layer most people work at is not where the tooling actually leaks. Think about it; how many times have you heard someone say that they dont use the impacket example scripts because they write their own? If you look at what Ive touched on here, you quickly realize if you ever imported impackets kerberos/smb/dcerpc protocol implementations then some if not all the detections still apply to you.
The third is a small one and it’s about specifications. Microsoft’s [MS-*] documents and the relevant RFCs are, I’ll be honest, a slog. They read like tax law but with contradictions that require you to bounce between observing on the wire activity or the Appendix B Product Behavior(which also half the time doesn’t clarify whats needed). But they are also where the ground truth lives, and they are surprisingly often the place where a finding I have had an idea on crystallizes.
The SPNEGO/AP-REQ thing went from confusing parser error to clean fingerprint the moment I cross-referenced the ASN.1 application class tags. Finding out that Impacket sending LM challenge responses is a fingerprint because MS-NLMP Appendix B footnote <49> states “all Windows versions from Windows XP onward suppress the LMv2 response when MsvAvTimestamp is present, sending 24 zero bytes instead”. That’s a pattern. Spec-reading is one of the most underrated skills in this corner of the field, and the payoff curve is non-linear for those whom take the effort to do it. It also helps in allowing you to understand with the goal of breaking/abusing implementation of important services/protocols in Windows.
To the people building tools and developing tradecraft
If you’re a red team operator and you’ve made it this far: take this as a nudge. The tools you reach for every engagement are doing things on the wire that you likely never looked into deeply enough to figure out they’ve been giving you away. No one has to do what I’ve done here to get an equal magnitude of benefits. But picking up one protocol to dig into, be it Kerberos, NTLMSSP, SMB, or whichever to capture some of your own tooling’s traffic alongside the equivalent native Windows behavior, and just look. Many I imagine would end up finding something that leads them down their own side quest. And whatever you may find it’ll either teach you something about the protocol you didn’t know, or it’ll show you a fingerprint you can actually do something about. Both outcomes are wins as I have personally learned going through this project.
And don’t write off the boring stuff. The walls of dry, dense specification text that everyone (including me) groans about, are where the magic actually comes from. Some of the more genuinely satisfying moments of my career so far have come from reading specs nobody wants to read and converting that into something practical. It’s worth the slog.
To the defenders
Use the repo as a reference, not a copy-paste alert library unless you want your SIEM to blowup.. Some indicators in there are strong enough to alert on standalone in the right environment. Most are better as scoring features that get powerful when you cluster them. Treat it the way you’d treat any detection input: validate against your own baseline, promote gradually from enrichment to hunting to alerting, and prefer clusters of behavior over single-field assumptions.
A client emitting raw NTLMSSP over DCE/RPC, missing verification trailers, an Impacket-style auth_context_id, and unusual WMI activation behavior all from the same host in the same window is, in practical terms, almost certainly not a Windows machine. That’s the kind of fingerprint that doesn’t get patched out trivially and one you can exploit to catch the next adversary or red teamer using Impacket in your environment.
Closing thoughts
This is, as these things always are, a cat-and-mouse game. I have no doubt that some of the indicators in the repo will be obsolete within a release or two of someone deciding to take cleanup seriously. When that happens, I genuinely hope whoever picks up the next round outdoes both me and the rest. That’s how the field gets better.
In the meantime, feel free to open issues if I’ve gotten something wrong, want to add a protocol, or just want to chat. Im here for all of it.
The gap I found when I started this project was bigger than I expected. But gaps are just places where the next person can build. I hope you outdo me.