TL;DR

The current implementation of the shadow credentials attack in the Impacket framework, most notably used by the ntlmrelayx.py script, contains multiple bugs, leaving unique signatures on the NGC data structures written to the msDS-KeyCredentialLink LDAP attribute by malicious actors. Heuristics could be used to identify most malicious NGC keys, regardless of the hacktool they were generated by.

Technical Details

I noticed by chance that the current implementation of the shadow credentials attack (a term coined and popularized by Elad Shamir) in the Impacket framework produces malformed KEYCREDENTIALLINK_BLOB binary data structures, having the following issues:

  • The KeyHash entry should contain a SHA256 hash of all other entries, but it is only calculated from the KeyMaterial entry.
  • The KeyCreationTime entry should be in the FILETIME format but is incorrectly skewed by 1600 years.
  • The DeviceId entry is expected to be present only on user accounts, but it is populated for computer accounts as well. And instead of referencing existing device objects, it contains random values.

These bugs are located in the shadow_credentials.py script, which is then optionally used by the ntlmrelayx.py script when performing the NTLM Relay attack against the LDAP protocol. The shadow credentials attack was originally implemented in version 0.10.0 of Impacket, released in May 2022. The bugs were introduced to the master branch in March 2024 and published in version 0.12.0, released in September 2024.

As a result, the DSInternals PowerShell module can be used to identify malformed NGC keys (AKA shadow credentials) that are still present in Active Directory (AD):

#Requires -Modules DSInternals,ActiveDirectory

 Get-ADObject -LDAPFilter '(msDS-KeyCredentialLink=*)' -Properties msDS-KeyCredentialLink |
    Select-Object -ExpandProperty msDS-KeyCredentialLink |
    Get-ADKeyCredential |
    Where-Object Usage -eq NGC

Sample output:

Usage Source  Flags      DeviceId                             Created    Owner
----- ------  -----      --------                             -------    -----
NGC   AD      None       ff53f58e-81a9-5d40-96bb-4980c91008ae 3625-02-23 CN=PC04,CN=Computers,DC=contoso,DC=com
NGC   AD      None       e49d674f-0259-44f3-a3bd-8343b76046fc 2025-02-02 CN=Administrator,CN=Users,DC=contoso,DC=com
NGC   AD      None       cfe9a872-13ff-4751-a777-aec88c30a762 2019-08-01 CN=John Doe,CN=Users,DC=contoso,DC=com
NGC   AzureAD None       fd591087-245c-4ff5-a5ea-c14de5e2b32d 2017-07-19 CN=John Doe,CN=Users,DC=contoso,DC=com
NGC   AD      MFANotUsed                                      2025-02-01 CN=PC01,CN=Computers,DC=contoso,DC=com
NGC   AD      MFANotUsed                                      2017-08-23 CN=PC02,CN=Computers,DC=contoso,DC=com
NGC   AD                                                      2021-06-11 CN=PC03,CN=Computers,DC=contoso,DC=com

The first NGC key in the output above, supposedly created in the year 3625 instead of 2025, can serve as a very reliable indicator of AD compromise (IoC). Moreover, all open-source implementations of the shadow credentials attack generate random values for the DeviceId attribute. It is therefore possible to use Microsoft’s WHfBTools PowerShell module to identify orphaned NGC keys with non-existing device identifiers.

The sample data also shows that the CUSTOMKEYINFO_FLAGS_MFA_NOT_USED flag in the CustomKeyInformation entry is used inconsistently by Windows 10+ computers, making it an unreliable indicator of malicious NGC keys.

Future Work

As other implementations of the shadow credential attack might contain similar issues, broader research on this topic should be conducted. Although hacktool authors will probably react to the information contained in this article and improve their implementations accordingly, these changes will not affect pre-existing shadow credentials.

Although the current version of DSInternals does not validate the KeyHash entries when parsing existing NGC keys, it correctly implements the hash calculation when generating new NGC keys or serializing existing ones. It should not be hard to implement hash validation as well.