How to Achieve Eternal Persistence Part 2: Outliving the Krbtgt Password Reset

In the previous blog post, we looked at how a password reset event can be captured and decrypted. Our quest to achieve eternal persistence in an Active Directory (AD) domain while being passive and undetectable is not yet complete. If the password of the krbtgt account is reset, we could end up in a scenario where the credentials that we have obtained thus far no longer work to decrypt network data.

To achieve our ultimate goal of eternal persistence, we need to take a look at how password changes are replicated. In this blog post we cover one aspect of AD’s password replication process, and in the next blog we dive into the generic replication process between domain controllers.

Before we dive into details, we need to understand how passwords are replicated. If you want to skip the fluffy details, scroll down to the 'Understanding the RPC call' section of the blog.

Replication

A domain has domain controllers (DC) that handle all logic on behalf of the domain. If you want to authenticate, you prove your identity to the DC after which you will be granted access, or not. Realistically, a domain should have multiple domain controllers, either for reliability purposes or to minimize latency in the authentication process.

Domain controllers in the same domain should have an exact copy of all the data that is in the domain, except for some data only relevant for the DC itself. If we query DC A and DC B for an attribute on a user account, the returned value must be the same. If a user changes their telephone number or any other attribute, this attribute must be shared with other domain controllers in the domain. This is done using replication, where domain controllers share updated data with their replication partners. In smaller sized domains, this often means that every domain controller replicates to all other domain controllers, but this could vary depending on how the domain is constructed and the amount of domain controllers.

There are a few types of replication. For this scenario we go into detail for the following replication types:

    1. Normal replication
    2. Urgent replication
    3. Immediate replication

i. Normal replication: This is the default behavior for most changed values in attributes. Every given interval (which defaults to 15 seconds[1]), the domain controller that holds the new information will send out a replication notification to replication partners, saying Hey man, want some data? The partnered domain controller will respond, saying Gimme the good stuff! after which the replication process will begin.

ii. Urgent replication: The process for urgent replication is basically the same as normal replication. However, it differs depending on the attribute, as it will send out a notification regardless of the interval. For example, when the password policy of the domain is changed on DC A, it will send out a replication notification immediately to the configured replication partners, which will respond with Gimme the good stuff!

iii. Immediate replication: Urgent replication is designed to be faster than regular replication, but sometimes that is not fast enough. It takes multiple packets for the new information to be replicated to all other domain controllers in the domain and for specific types of information, this needs to be faster. Security information, such as secrets, should be available immediately. Every domain has a primary domain controller configured that has the final answer regarding secrets. It does a few more things, but that is outside the scope of this blog post. If you’re interested, search Google for FSMO roles 😊

 

If a user changes their password on DC A, the new password will be immediately sent to the primary domain controller (PDC) of the domain, without sending a replication notification. The new password will be sent using a Remote Procedure Call (RPC) called NetrLogonSendToSam. This behavior can be observed in Wireshark:

image-20240202-105111

The first red block shows the password reset being invoked. Before the response is sent back to the client, the domain controller (192.168.88.129) connects to the PDC (192.168.88.128) and forwards the new password using the NetrLogonSendToSam RPC call. If the password reset event happens on the PDC, then this RPC is not invoked since the new password is already known on the PDC.

This process allows the DC to do a second opinion when an authentication attempt fails. If Piet has changed his password on DC A and immediately after tries to authenticate on DC B, DC B will check with the PDC if the credentials are correct. This behavior can be observed in the following image: 

Untitled-20240202-105618(1)

In the example, we can see that ADDC03 forwards the (invalid) credential to ADPDC01 - which is the PDC - only to have it verified that the password is indeed incorrect.

So what exactly is this NetrLogonSendToSam RPC call?

Understanding the RPC call 

Googling this RPC call leads us to the technical documentation site of Microsoft[2], detailing the RPC call:

Screenshot 2024-05-27 at 11.07.30

Looking at the call specifications, we’re interested in what’s inside the opaque buffer, but the article above does not mention how the buffer is constructed. However, take note of the following comment: A buffer to be passed to the Security Account Manager (SAM) service on the PDC. The buffer is encrypted on the wire.

After doing some desk research, we find the documentation for SAM Server-to-Server messages[3]. The base message is constructed with the message type, size and the message itself, as can be seen in the following image:

Untitled-20240202-110335(1)

There various message types, but message type PasswordUpdate Request Message[4] looks quite promising. This message is constructed as follows:

Untitled-20240202-110357(1)

While reading the documentation, we noticed something interesting: All bits that can be set, as specified below, can be set in any combination by the requestor with the exception of LM and NT; these bits MUST both be set or both be cleared.[4] This sounds interesting and could be an indication that the data contains both NTLM and LM hashes.

We now know how the data should look more or less, let’s see if we can dissect the data that’s inside the NetrLogonSendToSam RPC call.

Deconstructing the RPC call 

If you want to create test data, you can find more details about how to do this at the end of this blog post.

After resetting a password on a non PDC, we can see that the password is sent to the PDC: 

Untitled-20240202-111027(2)

Wireshark is able to partially decrypt the packet, but not fully dissect the message. Some data is successfully decrypted, but a majority of the message is gibberish:

Untitled-20240202-111324(1)

Looking back at the technical documentation, a hint was already given: The buffer is encrypted on the wire. Let’s decrypt the complete packet ourselves, shall we? 

Decrypting the data

The RPC call is invoked over a secure channel. Data is encrypted using a session key, the message digest and a sequence number. Using Wireshark it is possible to find the session key, by expanding Auth Info → Secure Channel Verifier and expand the expert section.

Untitled-20240202-111410(1)

When taking a look at the frame number, it becomes apparent that the frame has the actual session key in it. 

Untitled-20240202-111424(1)

To decrypt the contents of the RPC call, the package digest must be used as Initialization Vector (IV) to decrypt the sequence number. This value must be concatenated with itself and can then be used as IV. The session key needs to be XOR'd with 0xf0 after which we can decrypt RPC call. More info can be found here: https://github.com/billchaison/securechannel

Using this info, the message can be decrypted manually and results in (more or less) the same output as Wireshark:

Untitled-20240202-111632(1)

 

The data is encoded using Network Data Representation (NDR). During testing, we were able to workaround the need for a decoder, but given the limited sample size it could be possible that an NDR decoder is needed for more reliable decoding.

The MessageType field contains - well - the type of message. The following options are available[3]:

Untitled-20240202-111719(1)

The message in the example above is the opaque buffer. The buffer itself is re-encrypted using the same session key, but without being XOR'd with 0xf0. If we decrypt the buffer with this key and an empty IV, we can see some readable data. This resembles with the structure that we identified earlier on.

Untitled-20240202-111819(1)

Looking at the technical documentation, there are a few things that stand out:

  • When MessageType is set to 0x00000000, the message should contain an NTLM hash and an LM hash
  • No reference to a sAMAccountName or other identity reference can be found in the documentation, but the identity is referenced in the buffer. In the example above, the identity is set to pieter.pieterson.

The OffsetLengthArray contains information what info can be found where. It is an array with members of 8 bytes. The first 4 bytes indicate where the data in the buffer starts and the other 4 bytes refer to the length of the data. The order of which data are stored is fixed. Seen from the end of the array:

  • NTLM hash
  • LM hash
  • UserId

Untitled-20240202-111942(1)

By passively sniffing network traffic, we successfully decrypted and recovered the NTLM and LM hash of the new password set on the account of piet.pieterson. There are more ways to gather hashes in an AD environment, however, it is quite rare to obtain LM hashes. Even if the AD environment has been configured to not store and use LM hashes, domain controllers will still send the LM hash over the wire to the PDC, only to be omitted when saving it to ntds.dit. Of course, configuring a password longer than 14 characters will mitigate this issue and will result in an LM hash for value null.


By using TShark, this whole process has been automated. In the following snippet we see a password reset event for the user piet.pieterson followed by the NetrLogonSendToSam data sent to the PDC. 

image-20240202-142141(2)

Source of the tool, pcaps and key data can be found on our GitHub page: https://github.com/huntandhackett/PassiveAggression

But wait, there's more!

Being able to recover password hashes is nice, but intercepting and decoding the password reset event itself ultimately yields the same level of access. However, there is a surprising element to this call that allows us to step up our level of passive persistence.

If a domain is compromised, one of the key remediation steps is to reset the password of the krbtgt account twice. Intercepting these password reset events is not useful, since according to the Microsoft documentation [5]The password that you specify isn't significant because the system will generate a strong password automatically independent of the password that you specify.“

However, once reset on the domain controller, the password replication process is the same. That means that the new password of the krbtgt account is sent to the PDC using - you guessed it - the NetrLogonSendToSam RPC call!


We’ll go over why this is not a fluke or bad practice in the next blog post but for now, let’s enjoy the glorious sight of a new krbtgt hash, delivered at our digital doorstep:

image-20240202-140255(1)

By comparing this hash with the hash in the ntds.dit database, we know that this is the new hash of the krbtgt account and not the hash representation of the clear text password that we entered during the password reset.

Resetting this password directly on the PDC prevents the data being shared using the NetrLogonSendToSam RPC call. However, other domain controllers in the domain would still need to be notified of the newly configured password, otherwise replication will stop working. This is where other forms of replication comes to play and will be looked at in the next blog post.

Checking our goals

In the previous blog post, a few goals were established to determine if a new technique would be useful. This new method can be used to survive a remediation process, but only if the password of the krbtgt account is reset on a non-primary domain controller. If a primary domain controller is used to invoke the password reset, then the remediation process would be effective. Therefore, this method cannot be used to survive the remediation process. Further, this technique yields outdated password hashes, which are very useful - especially non-null LM hashes - but serve a different purpose. In some scenarios – such as setting up a NETLOGON session where the NTLM hash is used to generate an encryption key to be used with AES128-CFB encryption – this is fine. But authenticating using the Kerberos protocol would result in tickets being created using obsolete encryption or hashing algorithms, which is not considered opsec safe.

 

Surviving Remediation Image 3 ENHANCEDFigure 1. Results vs pre-agreed goals for eternal persistence

Create test data

Set up a Wireshark and an Active Directory environment. Refer to the previous blog post for pointers on how to do this.

Determine which domain controller holds the PDC FSMO role by running netdom query fsmo on a domain joined computer or a domain controller. Take note of another domain controller not having this role, connect to it and initiate a password reset of a user account. Examples can be found in the previous blog post.

After a few seconds, you should see RPC_NETLOGON API calls popping up in Wireshark, including the NetrLogonSendToSam RPC call: 

Untitled-20240202-111027(3)

References

  1. https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/modify-default-intra-site-dc-replication-interval#more-information
  2. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrpc/b06e6b30-fe57-4e0f-ba1a-5214c953a5df
  3. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sams/04428101-3a8d-48fe-a324-7206cf8f8bc3
  4. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sams/e6d9295f-dbb8-46a5-98f7-f4d3f970f36b
  5. https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/forest-recovery-guide/ad-forest-recovery-reset-the-krbtgt-password

 

Keep me informed

Sign up for the newsletter