Catching Credential Guard Off Guard

Oct 23 2025
Share
By: Valdemar Carøe • 36 min read

TL;DR Due to new security features in Windows and the lack of existing research, we set out to find ways to extract credentials on fully patched Windows environments with modern protections enabled. This resulted in a new generation of credential dumping techniques.

Credential dumping remains one of the most effective techniques used by adversaries and red teamers alike to perform post-exploitation and move laterally in Windows environments. Due to the scale of this threat, Microsoft introduced Credential Guard with Windows 10 in 2015, as a way to protect credentials and secrets by isolating them with Virtualization-Based Security.

The usage of Credential Guard renders known credential dumping techniques, such as those employed by the famous tool Mimikatz, ineffective. Despite being available for nearly a decade, Credential Guard has seen relatively little independent research compared to other Windows security features. To our knowledge, this blogpost by Oliver Lyak describes the only publicly documented method for interacting with Credential Guard to extract secrets, albeit in a noisy and often practically unfeasible manner.

Because of this gap in security research, we decided to take a closer look at Credential Guard and explore potential methods for extracting secrets, aiming to assess the real-world feasibility and impact of credential dumping against contemporary protections.

All research in this blogpost was conducted in a modern Windows environment consisting of fully patched Windows 11 workstations and Windows Server 2025 systems.

The Local Security Authority (LSA) in Windows

At the heart of Windows is the Local Security Authority (LSA), which is responsible for managing user authentication and security policies. The LSA is implemented in the lsass.exe executable and Microsoft describes the LSA as follows:

A protected subsystem that authenticates and logs users onto the local system. LSA also maintains information about all aspects of local security on a system, collectively known as the Local Security Policy of the system.

While the LSA is responsible for user authentication in Windows, the subsystem does not implement authentication protocols directly, but rather delegates user authentication flows to Security Support Providers (SSPs), which are modular components plugged into the LSA to extend support for different authentication protocols and mechanisms through the Security Support Provider Interface (SSPI).

An SSP typically implements a set of Security Packages (SPs), which provides the actual implementations of a security protocol. Some SSPs are also classified as Authentication Packages (APs), if they implement authentication logic used to determine whether to permit a user to log onto a system.

By default, Windows ships with several SSPs, each registered and managed by the lsasrv.dll component of the LSA. Some of the most important Security Packages included in these are:

  • Negotiate (lsasrv.dll) – A broker that negotiates which security protocol to use.
  • NTLM (msv1_0.dll) – The legacy user protocol.
  • Kerberos (kerberos.dll) – The default domain protocol.
  • Schannel (schannel.dll) – The SSL/TLS protocol.

The SSPI acts as a standardized API layer that allows applications to use SSPs to authenticate users, establish secure channels, and access context-based cryptographic functions to provide confidentiality and integrity, without having to understand how an authentication protocol works.

The SSPI authentication flow follows a predefined sequence:

  • Obtain a handle to your credentials.
    • Both the client (sender) and server (receiver) of an authentication flow must call AcquireCredentialsHandle to obtain a handle that represents their credentials.
  • Establish a security context.
    • The client must call InitializeSecurityContext to generate a negotiation request and/or process a negotiation response.
    • The server must call AcceptSecurityContext to process a negotiation request and/or generate a negotiation response.
    • This process repeats until both sides have established a secure context.
  • Exchange messages privately.
    • Once a secure context has been established, both the client and the server can use EncryptMessage to provide confidentiality and integrity for outbound data and DecryptMessage to validate confidentiality and integrity for inbound data.

The following diagram illustrates this authentication flow and subsequent interactions.

Applications that need to communicate directly with an authentication package for functionality which does not fit into the SSPI authentication flow, must do so through the following sequence:

  • Obtain a handle to LSA.
  • Locate the package identifier for the authentication package.
    • Applications must call LsaLookupAuthenticationPackage to retrieve the internal identifier for the authentication package of choice (NTLM, Kerberos, etc.).
  • Exchange messages with the authentication package.
    • Applications must call LsaCallAuthenticationPackage to send and receive data from the chosen authentication package, enabling authentication and related security functions.
  • Sidenote: This workflow can be simulated using LSA Whisperer by Evan McBroom, which uses these functions to interact with predefined security packages in the LSA.

While the SSPI authentication flow works for authentication to applications, there is a different flow for interactive logins. When a user logs into a local system through the Logon UI or into a remote system through the Remote Desktop Protocol (RDP) client, their credential material is passed to the LSA, and then delegated to the responsible authentication package.

For example, an interactive logon with a local account will be delegated to the Microsoft v1.0 (MSV1_0) authentication package, which compares the NT hash of the supplied password to the hash stored for the target user in the local Security Accounts Manager (SAM) database.

If an authentication attempt succeeds, the LSA creates a logon session and stores the provided credentials and derived secrets in a global structure within lsasrv.dll called LogonSessionList, enabling Windows to deliver Single Sign-On (SSO) for subsequent authentication requests.

The Traditional Credential Dumping Technique

Since interactive logons result in the supplied credentials and derived keys being stored in the global LogonSessionList structure in lsasrv.dll, it is possible to parse this structure by reading the live memory of the LSA (or a memory dump of the LSA) to extract sensitive information, including plaintext passwords and NT hashes.

The go-to tool for carrying out the above-mentioned technique is Mimikatz by Benjamin Delpy, which is considered the Swiss Army knife of Windows post-exploitation and credential dumping.

However, because Mimikatz inspects volatile memory structures and relies exclusively on binary pattern scans to locate the LogonSessionList structure, both of which can change significantly between Windows versions and major updates, it requires frequent updates to remain effective. In addition, accessing the memory of a privileged process such as lsass.exe is extremely noisy, and modern EDRs will raise alerts when someone attempts to dump credentials from the LSA.

To counter such techniques, Microsoft introduced the Protected Process Light (PPL) security feature for the LSA. When lsass.exe runs as a PPL process, untrusted user-mode applications can no longer obtain read-permissible handles to its memory, and security packages loaded by the LSA must be signed with a trusted certificate.

While PPL remains an effective hardening measure, security researchers have demonstrated several ways to bypass it. These include user-mode attacks, such as the one detailed in this blogpost by James Forshaw, which allows injection into processes protected by PPL, as well as kernel-mode attacks, such as the one used by PPLKiller, which strips the PPL protection from the lsass.exe process and restores read access to its memory space.

Credential Guard

To address the limitations of PPL and provide stronger protection against credential dumping, Microsoft introduced Credential Guard, a modern and powerful security feature built on top of Virtualization-Based Security (VBS).

On standard Windows systems without VBS, the bootloader loads the operating system kernel directly into memory at startup. This makes the kernel the most privileged entity in the system, and any access to it effectively grants unrestricted control over all system resources.

With VBS enabled, however, the bootloader first launches a hypervisor, which then runs the operating system alongside additional isolated kernels (micro operating systems), all of which operate in separate physical memory spaces. Without an exploit that allows an attacker to escape the hypervisor, gaining access to the Windows kernel no longer grants unrestricted control over more privileged system resources, including the isolated kernels created by VBS.

One of these isolated kernels is the new Secure Kernel (securekernel.exe), which operates at a higher privilege level within the hypervisor layer than the operating system kernel (ntoskrnl.exe). The Secure Kernel hosts submodules called trustlets, which provide specialized, security-critical functionality. To ensure their integrity, these trustlets can only be loaded by the secure kernel if they have been signed with a valid Microsoft certificate.

The secure kernel trustlet used by Credential Guard is the Isolated LSA (lsaiso.exe), which hosts multiple support interfaces for authentication, and can be called over Advanced Local Procedure Call (ALPC) or Remote Procedure Call (RPC).

This architecture is visualized in the illustration below.

The Isolated LSA (lsaiso.exe) is considered a critical system process, and attempting to stop or restart the process will cause the operating system to crash. Additionally, the support interfaces for NTLM and Kerberos each include initialization functions that return a context handle, which can only be called once during the lifetime of the Isolated LSA process. These are both called by the standard LSA during system boot, which means no other application on the system can obtain these context handles or interact with these interfaces from the Isolated LSA.

Furthermore, the Isolated LSA performs cryptographic operations by invoking the EncryptData and DecryptData syscalls from the Secure Kernel, both of which rely on a per-session key that is randomly generated every time the Secure Kernel initializes.

The Kerberos and NTLM support functions within the Isolated LSA are called KerberosIum and NtlmIum, respectively, with the “Ium” suffix standing for Isolated User Mode. Since the standard LSA now relies on the Isolated LSA to handle many authentication protocol functions, the LSA no longer stores credentials and derived keys in plaintext, but rather in an encrypted format that only the Isolated LSA can decrypt, using the per-session key managed by the Secure Kernel.

If the LSA needs to generate an AP-REQ authenticator for Kerberos authentication, it calls the KerbIumBuildApReqAuthenticator function from the KerberosIum interface, instead of creating the AP-REQ authenticator itself.

As a result, it is no longer possible to obtain Kerberos or NTLM credential material by dumping the LSA process, and a more sophisticated approach is required to interact with these isolated secrets.

To interact with the Isolated LSA interfaces, we would need to inject code into lsass.exe or load a custom security package, and then extract the context handle for the desired interface, before we can invoke any functionality over ALPC or RPC.

However, as previously mentioned, accessing the memory of the LSA is extremely noisy, and modern EDRs will raise alerts if someone attempts to inject code into the LSA. Additionally, if the LSA is running in PPL mode, then injecting code into the LSA would become more difficult and loading a custom security package would require the containing library to be signed with a trusted code-signing certificate, further limiting the feasibility of interacting with the Isolated LSA directly.

Therefore, the key problem we need to solve is how to communicate with the Credential Guard interfaces in a clean and unobtrusive manner that is not detected by EDR nor blocked by PPL protection.

Remote Credential Guard

A potential solution to our problem is Remote Credential Guard (RCG), a security feature for Remote Desktop Services (RDS), formerly known as Microsoft Terminal Services (MSTS).

In Windows 8.1 and Windows Server 2012 R2, Microsoft introduced a security feature called Restricted Admin mode for RDP connections, which would allow administrators to connect to a remote host without sending their credentials, thereby protecting the account from credential dumping attacks on the remote host. While effective at stopping credential dumping from a remote host, the feature also had a massive limitation, in that credentials were not forwarded to the remote host and therefore could not be used to access other services from the remote host.

To address this problem, known as the “Kerberos double-hop” issue, Microsoft introduced a new security feature known as Remote Credential Guard (RCG). The feature builds on two Microsoft protocols to protect credentials during RDP sessions:

  • The “Remote Desktop Protocol: Authentication Redirection Virtual Channel Protocol” (MS-RDPEAR) enables the use of credentials over an RDP connection without exposing those credentials or their derived keys to the remote system.
  • The “Credential Security Support Provider Protocol” (MS-CSSP) allows the client to securely delegate credentials to the remote host.

In practice, RCG uses MS-CSSP to transfer encrypted credentials from the client to the remote host, but these credentials cannot be decrypted by the remote host itself. Instead, MS-RDPEAR is used to redirect authentication operations back to the client whenever they are needed.

This design ensures that plaintext credentials and their derived keys never reside on the remote host, while still enabling the remote host to authenticate to other services because it can request the client to perform authentication operations on its behalf.

However, from our point of view that is not necessarily a problem, as what we are looking for is a way to communicate with the Credential Guard interface on the client. When RCG needs to perform authentication using Kerberos or NTLM, it communicates with the client through the KerbCredIsoRemote or NtlmCredIsoRemote interfaces. These remote interfaces then relay functionality into different internal interfaces depending on the state of Credential Guard.

If Credential Guard is enabled, the requests are routed into the KerbCredIsoIum interface or the NtlmCredIsoIum interface, which is the Credential Guard interface for the respective protocols.

If Credential Guard is disabled, the requests are routed into the KerbCredIsoInProc interface or the NtlmCredIsoInProc interface, which is the standard interface for the respective protocols.

Counter-intuitively, this means that RCG works whether or not Credential Guard is enabled, but also that we can communicate directly with the Credential Guard interface through RCG without having privileged access on the client from which we are initiating the RDP connection.

Striking Up A Conversation With Remote Credential Guard

At this point, we decided to establish a security context with our localhost LSA in a small proof-of-concept application by calling InitializeSecurityContext and AcceptSecurityContext repeatedly, so we could attempt to communicate with the Credential Guard interfaces.

The first problem we ran into was that redirected credentials were never shipped through our established security context, so we decided to take a deeper look at what was going on in the Terminal Services Security Support Provider (TS SSP), which is implemented in TsPkg.dll.

In the LSA, all user-mode API calls, such as InitializeSecurityContext or AcceptSecurityContext are delegated to callbacks in the responsible SSP. In the event of InitializeSecurityContext, the designated callback is called SpInitLsaModeContext. While looking at SpInitLsaModeContext, we found that the redirectable credentials were only obtained by the client (and shipped to the remote host) if a specific flag on the credential handle was set.

Additionally, we kept entering this if-statement that jumps to LABEL_248, effectively skipping the above code for obtaining the redirectable credentials.

The if-statement is only entered if a specific member of our context handle is set to a non-zero value, and if we backtrace the context handle member, we see that it is set when TSIsLoopback returns true.

In practical terms, this means that we cannot establish a security context with the local LSA, as the Terminal Services SSP includes a mechanism that detects local authentication and prevents the delegation of credentials in those cases. Both the Kerberos and NTLM security packages have similar mechanisms.

One of our initial thoughts was that we could just exclude the pszTargetName parameter, but unfortunately, the SSP does not ship redirected credentials if the client is not authenticated over Kerberos, and if we exclude the pszTargetName parameter, the authentication will fall back to NTLM.

This resulted in our first major requirement for the ability to communicate with Credential Guard, as we need to have privileged access to a remote host and execute in the context of SYSTEM on that same remote host before our calls to AcceptSecurityContext generate valid tokens that will correspond to the target specified in our client calls to InitializeSecurityContext.

After having passed the loopback restriction, the if-statement that contains the functionality to obtain the redirectable credentials was still being passed over, so we needed to also figure out how to coerce the credential handle to contain the correct flag.

The callback for AcquireCredentialsHandle is SpAcquireCredentialsHandle, and while traversing this function, we found that the flag field on the credential handle was inherited from a variable that we named Flags in the image below.

The Flags variable is the product of a call to TSCaptureSuppliedCreds.

Inside TSCaptureSuppliedCreds, we found that the pAuthData parameter that we passed to the AcquireCredentialsHandle function was parsed as a CREDSSP_CRED object, and if the Type field of the object was set to 0x64 (100), which is CredsspCredEx, then Flags would be set in a call to TSUnpackCredEx.

Afterwards, pAuthData would be set to the Cred field of the CREDSSP_CRED_EX structure, which is a nested CREDSSP_CRED object, and parsing would continue for other types.

Since the CREDSSP_CRED_EX structure contains a Flags field, we can assume that this is the flag that we need to populate to enter the desired branch. The only valid flag for this object field is CREDSSP_FLAG_REDIRECT, which makes sense considering we are attempting to obtain a redirected credential.

For the nested CREDSSP_CRED structure, we found that if we set the Type field of the object to either CredsspSubmitBufferBoth (50) or CredsspSubmitBufferBothOld (51), we did not have to supply any credentials, and the SSP would default to using our primary credential. A valid call to AcquireCredentialsHandle that passed the checks in SpInitLsaModeContext is shown below.

After having split the client and the server part of the proof-of-concept application into separate applications and sending the negotiation tokens between the two applications using network sockets, we managed to establish a security context and should finally be able to interact with the Credential Guard interfaces on the client – or so we thought…

The input that must be passed to an RCG call is wrapped in multiple layers of abstraction, and the MS-CSSP layer requires encryption using the established security context. That should not be a problem as we have already established a security context and can call EncryptMessage using the context handle, right?

Unfortunately, no. It turns out that the Terminal Services SSP manages an underlying Negotiate SSP security context, and that the security context used for encrypting the MS-CSSP layer of the input data is the security context established by the Negotiate SSP. Since the Terminal Services SSP does not provide any method by which we can obtain access to the underlying Negotiate SSP security context, we cannot encrypt our MS-CSSP payload.

At this point, we were too invested, so we decided to re-implement the Terminal Services SSP layer for the server (i.e., implementing the callback handler for AcceptSecurityContext), so that we could get access to the underlying Negotiate SSP security context handle.

Reimplementing The Terminal Services SSP

The Terminal Services SSP authentication flow that we want to reimplement is illustrated in the server part of the diagram below.

Out of respect for the reader, and for the sake of brevity, we will not go through the process of how we reverse-engineered the protocol, but the general idea illustrated in the diagram above, is that the Terminal Services SSP transfers authentication information between the client and server through a structure called TSRequest.

The TSRequest structure has multiple fields that, when populated, must be handled by the server and a result must be included in the response to the client:

  • Version – This field contains the version of the TSRequest structure.
  • NegoTokens – This field contains tokens that must be passed to the Negotiate SSP to establish the underlying security context. Depending on the negotiated protocol, this can include Kerberos authentication requests or NTLM authentication requests.
  • PubKeyAuth – This field depends on the Version field. In modern versions (5 or 6), this field contains a SHA256 hash of the public key of the certificate presented by the server during SSL/TLS handshake when the initial RDP connection was established.
  • ClientNonce – This field is only included in versions 5 or above, and contains a client nonce that was included in the SHA256 hash used to generate the PubKeyAuth field. This must also be included when generating a response to the PubKeyAuth field.
  • AuthInfo – This field contains the credentials delegated by the client, which must be sent over MS-RDPEAR by the remote host whenever it requires authentication operations to be carried out by the client.

When the NegoTokens field is set, we simply forward them to the Negotiate SSP whose security context we are trying to establish, and if the Negotiate SSP produces an output, we attach it to a TSRequest that we will send back to the client.

When the PubKeyAuth field is set, the server must first decrypt the PubKeyAuth field using the security context established with the Negotiate SSP, and generate a SHA256 hash of its public key using the ClientNonce field and a static salt “CredSSP Client-To-Server Binding Hash”. By comparing this against the decrypted PubKeyAuth field, the server verifies that the client has knowledge of its public key and that the client was able to encrypt the SHA256 hash using the established Negotiate SSP security context.

The server then generates a response SHA256 hash to the client using the ClientNonce field and a static salt “CredSSP Server-To-Client Binding Hash”. This is then sent back to the client, such that the client can verify that the server was able to encrypt the SHA256 hash using the established Negotiate SSP security context.

When the public key authentication is passed, the client sends a final request that contains the AuthInfo field. The server must decrypt the AuthInfo field and decode it into a TSCredentials structure, which can contain either passwords, smartcards, or redirected credentials.

Once we have obtained the AuthInfo structure, the authentication is complete, and we should have access to both the Negotiate SSP security context for encrypting or decrypting messages between the client and server, as well as the redirected credentials for the user.

The redirected credentials contain both primary logon credentials for the negotiated protocol, which we know is Kerberos, as well as supplemental credentials for security packages that the redirected credential supports, such as NTLM.

With these at hand, we can proceed to call functions through the KerbCredIsoRemote or the NtlmCredIsoRemote interfaces to interact with Credential Guard on the connected client.

In order to create a valid request for the Terminal Services SSP, we have to perform the following steps:

  • Create an input structure for the function we want to invoke.
  • Serialize the input using Remote Procedure Call (RPC) Type Serialization.
  • Wrap the serialized input in a TSRemoteGuardInnerPacket structure.
    • Encode the object as an ASN.1 structure.
    • Encrypt the object using the Negotiate SSP security context.
  • Wrap the TSRemoteGuardInnerPacket in an MS-RDPEAR payload structure.
    • The MS-RDPEAR payload must have its TsPkgContext field set to the upper part of the Terminal Services SSP context handle from the client.
  • Call directly into the Terminal Services SSP via the LsaCallAuthenticationPackage API.
    • Using the flow described in the beginning of this blogpost.

If the function succeeds, the call to LsaCallAuthenticationPackage will return an output buffer that we must process in reverse to obtain an output structure for the function we invoked:

Calling Credential Guard’s Bluff

After we had finally obtained the invocation primitive that we needed, we decided to play around with the Credential Guard interface to figure out if we could gain access to reusable credentials.

The first thing we tried was to obtain a Kerberos TGT. In order to do this, we had to decipher the redirected credentials structure that we received in the AuthInfo field of the final TSRequest.

The AuthInfo for the primary credential was a KERB_TICKET_LOGON object, which contained a TGS for our remote host and a TGT for the client user. The session key of the TGS could be decrypted using the long-term key of the remote host (e.g., the Kerberos key derived from its computer account password), and the encrypted part of the TGT could be decrypted using the session key from the TGS.

However, even after decrypting the encrypted part of the TGT, its session key was an encrypted blob generated by the Credential Guard instance running on the client. We then speculated that maybe if we renewed the TGT through a TGS-REQ, the KDC would return a TGT where the key is not an encrypted blob from Credential Guard.

In order to do that, we had to forge a TGS-REQ for the “krbtgt/domain” service principal name, but since we did not have access to the session key of the TGT, we had to invoke the following functions from the Credential Guard interface to obtain some of the pieces:

  • BuildEncryptedAuthData
  • ComputeTgsChecksum
  • CreateApReqAuthenticator

We then sent the TGS-REQ to the KDC, and decrypted the TGS-REP using a function from the Credential Guard interface called UnpackKdcReplyBody. However, the returned TGS-REP still contained a TGT whose session key was an encrypted blob from the Credential Guard instance on the client. This is consistent with what the Credential Guard section on Microsoft Learn says about the protection state of Kerberos tickets.

Effectively, we are unable to obtain reusable TGTs, but since regular TGS are not protected by Credential Guard, we could perform the same steps that we just did to request a ticket for an arbitrary service in the domain, and then inject the ticket on another host to impersonate the client from that endpoint. While this is interesting and mimics the theft of credentials, we are looking for something that is more readily usable and does not require us to have continuous access to the client host.

The next idea was to use the Credential Guard interface for NTLM to request NTLM challenge responses. For this purpose, the NTLM interface exposes the following functions:

  • NTLMv1: NtlmCalculateNtResponse
  • NTLMv2: NtlmLm20GetNtlm3ChallengeResponse

If we once again look at the Credential Guard section on Microsoft Learn, the documentation states that NTLMv1 cannot use our signed-in credentials when Credential Guard is enabled.

However, the caution message states that it is recommended that the credentials are not used, so we remain skeptical that NTLMv1 does not work with Credential Guard.

The AuthInfo with our redirected credentials contained supplemental credentials for NTLM, in the form of an MSV1_0_REMOTE_SUPPLEMENTAL_CREDENTIAL object. We can use the data in this object to forge an MSV1_0_REMOTE_ENCRYPTED_SECRETS object, and pass it to the NtlmCalculateNtResponse function along with a static challenge of 1122334455667788. The reason why we chose this static challenge will be explained in a later section.

According to the Credential Guard section on Microsoft Learn, this should not work, but lo and behold, we received an output from the function.

You can imagine our surprise…

If we decode the output, we should obtain an NtlmCredIsoRemoteOutput object, which contains the call ID of the function we invoked to obtain the output, a status ID containing an error code if the function failed, and the NTLMv1 response to our 1122334455667788 challenge.

The response length (24 bytes) matches the expected NTLMv1 format. To be certain that the response corresponds to the original password, we can run a targeted crack using a wordlist that contains only the suspected password as a sanity check.

As expected, the password cracks successfully, and we confirm that we were successfully able to interact with the Credential Guard interface through RCG and request responses to chosen NTLMv1 challenges, despite the statement on Microsoft Learn.

Unfortunately, we are still faced with an issue, as the technique requires us to have SYSTEM on a remote host, which is not always feasible. A much more likely scenario is one where we have the ability to join a machine to the domain and use the machine account as our remote host.

However, in that case, the machine does not have a physical host, so we would have to run the server part of our implementation on the client itself while impersonating the machine account of the remote host – but then the loopback check would become an issue again.

Since the Negotiate SSP recognizes whether received tokens originate from the same host, we would have to reimplement the Negotiate SSP and Kerberos SSP layers in the same manner as we did for the Terminal Services SSP.

Reimplementing The Negotiate SSP (And Kerberos SSP)

The server part of the Negotiate SSP and the Kerberos SSP authentication flows that we want to reimplement are illustrated in the diagram below.

Once again, out of respect for the reader, and for the sake of brevity, we will not go through the process of how we reverse-engineered the protocols, but instead focus on the general idea of how the protocols illustrated in the diagram above work.

When the client initiates a Negotiate SSP authentication flow, they send an InitialNegToken that contains a MechTypes list, which is a list of mechanisms (authentication protocols) supported by the client. Since we know that Terminal Services require Kerberos, we will be confirming that the list contains the “Kerberos 5 GSS-API Mechanism” (object identifier 1.2.840.113554.1.2.2). This token also includes a negotiation token for the expected target mechanism, which in our case is Kerberos.

In response to the InitialNegToken, the server sends back a NegotiationToken which contains a SupportedMech string denoting which mechanism from the client list that the server has chosen for the remainder of the negotiation. Additionally, the NegotiationToken contains a response to the client-supplied token from the chosen mechanism, which in our case is Kerberos.

The remaining communication between the server and client uses NegotiationToken to transfer tokens from the underlying mechanism, and in the last message sent by the client and server, they include a MechListMic, which is a message integrity code calculated over the MechTypes list to verify that the list was not modified in transit to downgrade the authentication security.

For the underlying Kerberos authentication, the client initiates the authentication flow by sending a User-to-User (U2U) TGT-REQ. If the server does not already have a TGT in memory, it sends an AS-REQ to the KDC and obtains its TGT in an AS-REP. The server then ships the TGT back to the client in a User-to-User (U2U) TGT-REP.

Using the TGT for the server, the client requests a TGS from the KDC (this is not illustrated in the above diagram), and presents it to the server by sending an AP-REQ. The server decrypts the key from the TGS in the AP-REQ using its long-term key, and then decrypts the AP-REQ authenticator with the decrypted key, after which it responds to the client with an AP-REP.

The encryption key for the established security context depends on factors that occurred during the Kerberos authentication flow in the following order of precedence:

  • If the server included an Acceptor Subkey in the AP-REP, this key is used.
  • Otherwise, if the client included an Initiator Subkey in the AP-REQ, this key is used.
  • Otherwise, the session key from the TGT is used.

Because we want to keep the authentication flow simple and ensure that our tool works reliably, we will always send an Acceptor Subkey in the AP-REP, so we can guarantee that we use the correct encryption key for the Terminal Services SSP communication.

Since our authentication flow is now a custom implementation, and we no longer have access to an established security context in the LSA, we have to reimplement the cryptographic functions that were previously available through the EncryptMessage and DecryptMessage functions.

The callback handler for the EncryptMessage function is called SpSealMessage, and similarly, the callback handler for the DecryptMessage function is called SpUnsealMessage. When we trace these in the Kerberos SSP, we end up in two functions called KerbSealMessageOld and KerbUnsealMessageOld. There is little to no documentation on how these functions work for AES128 (encryption type 17) or AES256 (encryption type 18), so we had to reverse-engineer these from scratch.

Once we have everything implemented, we can build a single streamlined application that can carry out both the client and server parts of the Terminal Services SSP authentication flow, and invoke functionality from the Credential Guard interfaces using credentials for an arbitrary machine account as the remote host without having physical access to another machine.

How The Turn Tables – Simulating Success

We start by creating a machine account, SRV10, using Powermad.

We can then perform the entire Terminal Services SSP flow by simulating a remote host using the machine account for SRV10$, which we just joined to the domain, and obtain the NTLMv1 response to the 1122334455667788 challenge for our current user.

This entire flow requires no privileges to execute, and we can therefore dump credentials for our current user in a crackable format, regardless of whether we are privileged or not.

But what if we are on a host that does not have Credential Guard enabled? Honestly, it does not matter. As we previously mentioned, the NtlmCredIsoRemote interface which is used by RCG on the remote host to communicate with the client for authentication operations, relay requests to either NtlmCredIsoIum or NtlmCredIsoInProc based on whether Credential Guard is enabled.

As a result, the RCG interface can communicate with Credential Guard if enabled, but also with the standard NTLM interface if Credential Guard is disabled, making the technique work in any case.

Now this is great, but we still require access to a machine account or SYSTEM on a remote host to leverage its machine account and perform the attack, but what if we cannot achieve either of these? Well, we have a solution for that as well!

What we did not elaborate on earlier, is that the reason we need a machine account is because the Kerberos protocol requires Service Principal Names (SPNs) as targets for authentication, and machine accounts by default have various SPNs configured – but we can also use other account types as the target “remote host” if they have a SPN configured.

Effectively, this means that either having access to a domain account with a configured SPN, or having write permissions over a domain account object (so we can configure SPNs) enables the technique.

However, while being restricted to dumping only the NTLMv1 responses for challenges directed at the current user might seem underwhelming, we can extend the technique even further.

As many readers will be aware, traditional credential dumping methods require privileges on the target host, and this technique is no exception when used to its fullest effect. If we have access to the SYSTEM context on the client host, we can impersonate tokens from other authenticated users and leverage the technique in the context of those users.

I’ll be honest, we were not initially sure if this was going to work…

NTLM Has Joined The Chat

So far, we have found ways to dump credentials for the current principal from an unprivileged context (or all principals from a SYSTEM context) on hosts with Credential Guard enabled by leveraging the RCG protocol. But what if we do not have access to the hosts where Credential Guard is enabled, but instead have access to a server where the client hosts are authenticated over RDP, potentially using the RCG protocol?

Let us assume that we have SYSTEM privileges on a server, SRV02, where administrators have authenticated over RDP using the RCG protocol, and Credential Guard is enabled on their client hosts.

If we try to use the previously discussed techniques, we will be met with the following exception.

Specifically, we receive error code 0x8009030e (SEC_E_NO_CREDENTIALS), which makes sense, as the users we are attempting to impersonate does not have any primary credentials bound to them, due to them leveraging the RCG protocol to relay authentication requests back to the client. So how can we possibly redirect these already redirected credentials?

Think about the following. How does a regular application that wants to perform authentication towards a service know if the user of the application is authenticated interactively on the host or is connected through RCG from a client where Credential Guard is enabled? Well, it does not.

So, how does the application perform authentication? It interacts with the SSP for the desired authentication protocol and expects Windows to take care of the rest – and we can do the same!

As pointed out to me by my colleague Evan McBroom, who is the developer of LSA Whisperer, the Microsoft v1.0 authentication package, which contains the NTLM security package, exposes an interface with security functionality for the NTLMv1 and NTLMv2 protocols.

One of these functions, MsV1_0Lm20GetChallengeResponse, is more or less identical to the NTLMv1 challenge-response functions we have previously invoked from the Credential Guard NTLM interface, but instead interacts with the standard NTLM security package.

Invoking the function requires SYSTEM privileges, but if we call the function with the previously described static challenge, and a logon session id (LUID) for the session of a target user, we can obtain NTLMv1 responses for any user on the host that has authenticated interactively on the host or has authenticated through RCG – even if Credential Guard is running on their client system. Furthermore, this technique does not require access to an account with a configured SPN, so it has even less limitations than the Credential Guard technique, but does not work on client systems where users are logged in interactively with Credential Guard enabled.

Interestingly, this technique also enables the extraction of credentials from interactively logged-on users on hosts where Credential Guard is not enabled, provided we have system privileges on the host.

So far, we have discussed various techniques for dumping user credentials on a system, each with different requirements. To simplify understanding, we have summarized these methods, their requirements and their ability to dump credentials protected by Credential Guard in the table below.

Dumping TechniqueRequires
SYSTEM
Requires
SPN Account
Can Dump
Credential Guard
Extract own credentials (via the Remote Credential Guard protocol)X
Extract all credentials (via the Remote Credential Guard protocol)
Extract all credentials (via the Microsoft v1 authentication package)XX

Now that we have covered almost every scenario of credential dumping on modern Windows systems that you will ever find yourself in, you might be wondering – why should anyone care about NTLMv1 hashes if they are not directly authenticatable?

Why Should We Care About NTLMv1?

The NTLMv1 cryptographic scheme, as illustrated in the diagram below, is considered very weak.

In short, the 16-byte NT hash of the user password is divided into three separate 7-byte chunks. Each chunk is used to derive a DES key, which encrypts the 8-byte server challenge. The three resulting 8-byte ciphertexts are then concatenated to form the final 24-byte NTLMv1 response.

Because the keys are derived from 7-byte (56-bit) chunks, each key has a limited keyspace, making it computationally feasible to recover the three keys independently. There exist pre-computed rainbow tables for server challenge 1122334455667788, which cover around 99.5% – 99.99% of the keyspace, allowing lookups that can take less than 30 seconds on specialized hardware.

Since we are recovering the NT hash instead of the actual password, a successful lookup can result in directly usable credentials (with Pass-The-Hash), regardless of the complexity of the underlying password. The NT hash is also equivalent to the RC4 long term key for Kerberos, and can be used to request TGTs and service tickets.

Previously, crack.sh operated a free service for performing rainbow table lookups to recover NT hashes from NTLMv1 responses, but was recently shut down due to maintenance issues. In its absence, a new free service was published at ntlmv1.com.

Once we submit a cracking task, we can monitor the task on the website, and after it has been recovered, we can obtain the NT hash. Please note that this website is not as fast as crack.sh, and may take a few minutes to perform the lookup.

If you aim to obtain the plaintext password instead, you could crack the NTLMv1 response using hashcat, but in that case, you would not have to use the 1122334455667788 challenge.

Defensive Considerations

We are not currently aware of any EDR products that monitor interactions with the LSA, and to our knowledge, there are no support functions that allow a third-party product to monitor these interactions in a non-invasive manner.

For EDR vendors, we recommend hooking the LsaCallAuthenticationPackage API, and monitor calls to the NTLM security package for NTLM responses to challenge 1122334455667788.

We currently do not have any defensive recommendations for detecting the RCG-based attack, as the input payload is encrypted and cannot easily be examined by an EDR product.

Conclusion

Credential Guard represents a significant step forward in protecting Windows credentials from traditional dumping techniques, but credential dumping is far from dead. Even in environments that leverage the newest protections implemented by Microsoft, credential dumping remains a viable post-exploitation technique for lateral movement.

Our exploration of the security feature in this blogpost highlights the strengths and limitations of the technology, while introducing novel methods for dumping credentials from both unprivileged and privileged contexts.

Disclosure Timeline

I disclosed the issue to Microsoft, stating that we can extract the redirected credentials of users connected to a server and use Remote Credential Guard from the server to communicate with Credential Guard interfaces on the client to obtain Kerberos tickets and NTLM responses.

In the disclosure timeline, we added three exit points for publishing our research:

  • 30 days – September 20, 2025 – If Microsoft did not confirm that the issue had been reproduced.
  • 60 days – October 20, 2025 – If Microsoft did not confirm that the issue would be serviced.
  • 90 days – November 19, 2025 – Non-negotiable disclosure deadline.

The disclosure occurred according to the following timeline:

  • August 21, 2025 – Disclosure to Microsoft through the MSRC portal.
  • August 29, 2025 – Microsoft asked for elaboration with follow-up questions.
  • September 3, 2025 – The follow-up questions were answered in detail.
  • September 16, 2025 – Microsoft has successfully reproduced the issue.
  • September 24, 2025 – Microsoft has concluded that they will not service the issue.

Try It Today!

The proof-of-concept DumpGuard tool developed for this research is available now on GitHub.

Join us in the #red-team channel in the BloodHound Slack for any questions or feedback, and feel free to report any issues on GitHub.

We hope you are as excited about this new research as we are!