NTLM By Default in PerfMon

November 7, 2025

Going NTLM-less is all the rage in Windows environments these days. NTLM is considered a "legacy" authentication protocol, the use of which is highly discouraged. Microsoft has declared as much: that NTLM is dead (dreprecated) and that it will be gradually phased out and that organizations should seek to reduce and remove as much NTLM usage throughout their enterprise as possible, effective immediately.

But as with most things in life, that's easier said than done.

There are still hundreds of programs, applications and services - both Microsoft and third party - that still use or rely on NTLM, either because of poor design, or misconfiguration. No one - not even Microsoft themselves - has a complete inventory of all the things lurking out there that still rely on or use NTLM.

Windows introduced some Group Policy settings that make it easy for you to completely disable NTLM in your environment. If you're lucky enough to be doing a green field deployment, you should give it a try. Just be aware that you are likely to encounter authentication problems with various apps and services (and even native Windows components, foreshadowing what this blog post is about,) that may still be trying to use NTLM.

Those policies are:

As time goes on we see more Windows organizations attempting to disable NTLM in their environments. Then those organizations encounter something that doesn't work right after disabling NTLM. Then they call us to help fix it.

After one brave customer enabled the Group Policies mentioned above in their environments, one of the things they noticed that broke was their monitoring software. The software that remotely monitors their Windows machines for things like CPU usage, disk usage, etc., the kind of performance monitoring that takes place in every enterprise.

A very important and essential activity that absolutely must work.

To make this as simple as possible, assume a domain controller named DC1.lab.contoso.com, and two member servers, SERVER1.lab.contoso.com and SERVER2.lab.contoso.com.

The customer found that even outside of their monitoring software, just Performance Monitor (perfmon) by itself would not work when attempting to connect from SERVER1 to SERVER2:

Perfmon: Unable to connect to machine

This unusual Kerberos error can also be observed at the same time (if the relevant debug logging is turned on) :

Event 3, Security-Kerberos
A Kerberos error message was received:
 on logon session 
 Client Time: 
 Server Time: 2:28:16.0000 11/6/2025 Z
 Error Code: 0x7  KDC_ERR_S_PRINCIPAL_UNKNOWN
 Extended Error: 
 Client Realm: 
 Client Name: 
 Server Realm: LAB.CONTOSO.COM
 Server Name: krbtgt/\
 Target Name: krbtgt/\@LAB.CONTOSO.COM
 Error Text: 
 File: onecore\ds\security\protocols\kerberos\client2\kerbtick.cxx
 Line: 12f7
 Error Data is in record data.

This is unusual because "krbtgt/\" has an extra backslash character after the name that shouldn't be there. "krbtgt" and "krbtgt/dc1.lab.contoso.com" for example, are normal and expected things for a Kerberos client to ask for. But "krbtgt/\" is not.

You'll see the same unusual Kerberos queries in a network trace. And as expected, the domain controller will be answering those queries with KDC_ERR_S_PRINCIPAL_UNKNOWN, because it's not a valid SPN to be asking for.

On the target server, we found events in the NTLM Operational log (again, assuming relevant logging is enabled,) that an attempt to use NTLM by the Remote Registry service was blocked:

Event 4002, NTLM
NTLM server blocked: Incoming NTLM traffic to servers that is blocked
Calling process PID: 3068
Calling process name: C:\Windows\System32\svchost.exe (remote registry service)
Calling process LUID: 0x3E5
Calling process user identity: LOCAL SERVICE
Calling process domain identity: NT AUTHORITY
Mechanism OID: (NULL)

NTLM authentication requests to this server have been blocked.

If you want this server to allow NTLM authentication, set the security policy Network Security: Restrict NTLM: Incoming NTLM Traffic to Allow all.

Despite all this, we find that we did actually successfully acquire a Kerberos service ticket for cifs/SERVER2.lab.contoso.com, even though the connection failed! That's because there are two authentications that take place when you connect to a machine remotely through Perfmon. The first connection to SERVER2\IPC$ worked correctly with Kerberos. But the second connection to the \pipe\winreg (remote registry) RPC tower failed.

The reason the Remote Registry service is involved is because at the API level, performance counter data is accessed just like registry data.

Here is a call stack of perfmon on SERVER1 trying and failing to connect to the remote machine:

DbgID ThreadID     User Kernel Position      Approx. System Time                       COM-Initialized
1     dbc (0n3516)   0s     0s 0000380B:0D5B Thursday, November 6, 2025 1:38:02.630 AM ApartmentType_STA

 # Call Site                                               Info
 0 RPCRT4!Ndr64ClientNegotiateTransferSyntax+0x783
 1 RPCRT4!Ndr64pClientSetupTransferSyntax+0x86d
 2 RPCRT4!NdrpClientCall3+0x944                             ncacn_np \\SERVER2:\PIPE\winreg
 3 RPCRT4!NdrClientCall3+0xf0
 4 ADVAPI32!PerflibV2EnumerateCounterSet+0x33
 5 ADVAPI32!PerfEnumerateCounterSet+0x1f91b
 6 pdh!PERF_MACHINE::EnumerateCounterSet+0x5a
 7 pdh!PERF_MACHINE::ConnectMachine+0x18c
 8 pdh!PERF_MACHINE::Connect+0x49
 9 pdh!PdhConnectMachineW+0x6a
 a pdhui!PdhiLoadNewMachine+0x82
 b pdhui!PdhiBrowseCtrDlg_MACHINE_COMBO+0x181
 c pdhui!PdhiBrowseCtrDlg_WM_COMMAND+0xe6
 d pdhui!BrowseCounterDlgProc+0xa5
 e USER32!UserCallDlgProcCheckWow+0x14b
 f USER32!DefDlgProcWorker+0xcb
10 USER32!DefDlgProcW+0x36
11 USER32!UserCallWinProcCheckWow+0x319
12 USER32!CallWindowProcAorW+0x6e
13 USER32!CallWindowProcW+0x8e
14 MFC42u!_AfxActivationWndProc+0x9d
15 USER32!UserCallWinProcCheckWow+0x319
16 USER32!SendMessageWorker+0x23c
17 USER32!SendMessageW+0x10f
18 USER32!IsDialogMessageW+0x224
19 USER32!DialogBox2+0x170
1a USER32!InternalDialogBox+0x14b
1b USER32!DialogBoxIndirectParamAorW+0x58
1c USER32!DialogBoxParamW+0x7d
1d pdhui!PdhUiBrowseCountersExHW+0x67
1e sysmon!BrowseCounters+0xd2
1f sysmon!CSysmonControl::AddCounters+0xc2
20 sysmon!SysmonCtrlWndProc+0x769
21 USER32!UserCallWinProcCheckWow+0x319
22 USER32!SendMessageWorker+0x23c
23 USER32!SendMessageW+0x10f
24 Comctl32_7ffe51720000!CToolbar::TBOnLButtonUp+0x19c
25 Comctl32_7ffe51720000!CToolbar::ToolbarWndProc+0x4099c
26 Comctl32_7ffe51720000!CToolbar::s_ToolbarWndProc+0x54
27 USER32!UserCallWinProcCheckWow+0x319
28 USER32!CallWindowProcAorW+0x6e
29 USER32!CallWindowProcW+0x8e
2a Comctl32_7ffe51720000!CallNextSubclassProc+0xa8
2b Comctl32_7ffe51720000!DefSubclassProc+0x67
2c Comctl32_7ffe51720000!TTSubclassProc+0xc9
2d Comctl32_7ffe51720000!CallNextSubclassProc+0xa8
2e Comctl32_7ffe51720000!MasterSubclassProc+0xa7
2f USER32!UserCallWinProcCheckWow+0x319
30 USER32!DispatchMessageWorker+0x1d2
31 MFC42u!CWinThread::PumpMessage+0x72
32 mmc!CAMCApp::PumpMessage+0x46
33 MFC42u!CWinThread::Run+0x96
34 MFC42u!AfxWinMain+0xbc
35 mmc!__wmainCRTStartup+0x1dd
36 KERNEL32!BaseThreadInitThunk+0x10
37 ntdll!RtlUserThreadStart+0x2b

And here is an example call stack from lsass on SERVER1 while processing one of these "krbtgt/\" requests:

DbgID ThreadID    User Kernel Position      Approx. System Time
1     290 (0n656)   0s     0s 00000B5E:085C Thursday, November 6, 2025 1:04:04.004 AM

 # Call Site                                            Info
 0 kerberos!__KerbGetTgsTicket+0x2c38
 1 kerberos!KerbGetTgsTicketEx+0x19c
 2 kerberos!KerbGetTgsTicketWithBranchEx+0x129
 3 kerberos!KerbGetServiceTicketInternal+0xeb7
 4 kerberos!KerbGetServiceTicketEx+0xce                 Ticket: SERVER2@\
 5 kerberos!KerbGetServiceTicket+0x78
 6 kerberos!SpInitLsaModeContext+0x2860
 7 lsasrv!WLsaInitContext+0x2f6
 8 lsasrv!NegBuildRequestToken+0x11c1
 9 lsasrv!NegGenerateInitialToken+0x51
 a lsasrv!NegInitLsaModeContext+0x2cc
 b lsasrv!WLsaInitContext+0x2f6
 c lsasrv!SspiExProcessSecurityContext+0x82e
 d SspiSrv!SspirProcessSecurityContext+0x28d
 e RPCRT4!Invoke+0x73
 f RPCRT4!Ndr64StubWorker+0xb79
10 RPCRT4!NdrServerCallAll+0x3c
11 RPCRT4!DispatchToStubInCNoAvrf+0x22
12 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1af
13 RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
14 RPCRT4!LRPC_SBINDING::DispatchToStub+0x2c
15 RPCRT4!LRPC_SCALL::DispatchRequest+0x33c
16 RPCRT4!LRPC_SCALL::QueueOrDispatchCall+0x131
17 RPCRT4!LRPC_SCALL::HandleRequest+0x837               Client: PID: 0x5b8 TID: 0xdbc
18 RPCRT4!LRPC_SASSOCIATION::HandleRequest+0x24d
19 RPCRT4!LRPC_ADDRESS::HandleRequest+0x181
1a RPCRT4!LRPC_ADDRESS::ProcessIO+0x897
1b RPCRT4!LrpcIoComplete+0xc9
1c ntdll!TppAlpcpExecuteCallback+0x280
1d ntdll!TppWorkerThread+0x448
1e KERNEL32!BaseThreadInitThunk+0x10
1f ntdll!RtlUserThreadStart+0x2b

The domain name is supplied as "\" instead of an actual domain name. So while a query for an SPN such as "krbtgt/lab.contoso.com" would be considered normal and expected, the domain is specified as just "\" so the query becomes "krbtgt/\".

There was initially an assumption that pdh.dll (performance data helper) was somehow forming this SPN incorrectly, however after TTD (Time Travel Debugging) review, we didn't find pdh.dll creating any SPNs at all. Perfmon simply passes exactly what the user has entered into that text box in the GUI (\\SERVER2) down to the underlying layers of RPC, SSPI, etc..

\\SERVER2 is not a valid SPN. Therefore it isn't valid for Kerberos. Therefore Kerberos fails, NTLM fallback is attempted, but that fails too because NTLM has been blocked by policy.

There is some code in the Kerberos client running in lsass that processes incoming target names and tries to make sense out of them. This code scans the string that was given to it - "\\SERVER2" in this case - from right to left looking for special characters such as @, /, \, etc.

When the code encounters its first backslash character, kerberos makes the assumption at that point we're dealing with an NT4-style name, such as CONTOSO\labuser. Therefore the kerb client assumes everything to the left of the rightmost backslash must represent the domain name. Even though in this case the only thing after the rightmost backslash is another backslash.

And so the domain name is assumed to be "\". Which of course we know is nonsense. Everything goes downhill after that.

So funny thing, now that we know about this logic, we can exploit it to our advantage. If we now go back to Perfmon and connect to \\SERVER2\ with an extra backslash at the end, Kerberos will work properly again, and Perfmon connects to the remote machine normally!

This has the stark implication that accessing remote performance counters in this fashion has been forcing people to use NTLM over Kerberos since day 1. For decades. And most people just never knew it until they completely disabled NTLM in their environment and exposed this problem..

This is... suboptimal behavior, and this naming syntax is treated inconsistently across the Windows ecosystem. But this is how the behavior has always been, for 25+ years. You must understand Windows was using the \\servername UNC format since before Windows even used Kerberos. And this misbehavior is now so deeply entrenched both inside of and outside of Windows, I'll be surprised if it's possible to make any change without breaking someone else.

For example, consider the PowerShell Get-Counter cmdlet, where using the "\\" UNC syntax is required.

The authentication team is not interested in implementing a fix for that very reason. They will just say "it is the application's responsibility to pass us a valid SPN if they want to use Kerberos." I can't really blame them, as I said, I'm sure any conceivable change they made would end up causing someone else to break.

I'll see if the Perfmon team will consider at least removing the "\\" prefix by default from the perfmon GUI. But again, we couldn't just stop supporting that syntax either. Imagine all the millions of people around the world who have been accessing remote perf counters in scripts, monitoring tools, etc., for decades, that would break if we stopped allowing that syntax.

All I know for sure is that the current behavior of "NTLM by default" cannot continue.