In part 1, we covered the prerequisite Windows internals knowledge to understand how the Mimikatz pass-the-hash (PtH) command is implemented. In this post, we begin reverse engineering the Mimikatz tool’s implementation of pass-the-hash.
In the context of this post, pass-the-hash involves leveraging legitimate authentication mechanisms built into Windows. As we previously discussed, an access token has authentication material mapped to it in LSASS to implement the single sign-on functionality for network authentication in Windows.
In this context, the idea behind exploiting PtH is to modify this cached authentication material in LSASS to use an attacker-controlled credential instead of the current user's NTLM hash for authentication to network resources. This technique allows an attacker to leverage a compromised user's NTLM hash to perform authentication to remote hosts without knowing the account’s password. Overwriting stored authentication material enables an attacker to use builtin Windows commands (e.g., dir to list files on a remote host) as Windows will automatically use the NTLM hash specified by the attacker.
There exist alternative methods of leveraging the PtH technique, such as leveraging a custom implementation of the NT LAN Manager authentication protocol alongside custom implementations of SMB. One example of this is the Impacket utility [6]. Our discussion of pass-the-hash omits network and protocol-level implementation details.
In part one, we mentioned that each process and thread has an access token associated with it. This access token specifies the process’s privileges on the local system, group information, and default DACL. We also discussed how an access token includes an authentication identifier that maps credentials cached in LSASS to an access token used when a process tries to interact with network resources such as file shares.
Mimikatz is capable of multiple modes of operation. If the end-user specifies the LUIDof the logon session, then Mimikatz overwrites the stored credential material for that session. If the operator specifies the username (using the /user option), then the Mimikatz tool will spawn a new process using the CreateProcessWithLogon function and overwrite the credential material associated with that logon. The Mimikatz process’s main thread will then use impersonation to impersonate that logon session using SetThreadToken.
After reviewing previous research on this subject, there appear to be three primary methods for overwriting stored credentials in the LSASS process. Hernan Ochoa discusses these design tradeoffs extensively in two separate presentations, “Pass-The-Hash Toolkit for Windows Implementation & Use” [4] and “WCE Internals” [1].
Ochoa outlines a pass-the-hash technique by injecting a DLL into LSASS and resolving and subsequently calling the relevant functions from within the injected DLL [1, Slide 23]. In this instance a signature is still used to identify the relevant functions (such as msv1_0.dll!NlpAddPrimaryCredential) [4, Slide 42].
Ochoa outlines a second method he has used to implement pass-the-hash by using the Microsoft Symbol Server to resolve the address of LogonSessionList[1, Slide 45]. This technique makes it much easier for the attacker to locate the appropriate addresses. However, it requires both an outbound HTTP connection to the Microsoft symbol server and large non-default DLL files that are not ordinarily present within a default installation of Windows (such as symsrv.dll and dbghelp.dll) [1, Slide 47].
The method used by Mimikatz involves using signatures to identify the location of symbols within LsaSrv.dll, such as the LogonSessionList global variable. This method works by scanning for a signature to identify instructions used to load the address of variables such as LsaLogonSessionList into memory. This technique is described in detail in later sections.
There are tradeoffs in each of these methods. The first method is likely the easiest to implement. However, it requires injecting a DLL into LSASS, which can be noticeable to endpoint security tools. The second method requires being able to query the Microsoft Symbol Server, which could introduce difficulties on systems without access to the internet. The third method is probably the best in terms of stealth and operational usage. However, it does appear to be the trickiest to implement, since the signatures will depend on the exact build of Windows running on the target system.
The developers of Mimikatz have used the signature-scanning method outlined by Ochoa. This method requires a significant amount of work to develop and maintain. When new versions of Windows are released, potential updates need to be made to adjust structure offset changes and changes to the source, which could otherwise break certain heuristics.
Mimikatz contains signatures specific to each major Windows release. These signatures can be leveraged as a heuristic to determine the location of two important structures; specifically, by scanning for a unique sequence of bytes within LsaSrv.dll. The first structure is the LogonSessionList and the second item is the LogonSessionListCount pointer. These structures are used to store a list of currently active Windows logon sessions.
The method employed by Mimikatz is quite ingenious. The idea behind this technique is that since both LogonSessionList and LogonSessionListCount are global variables, they can leverage a heuristic to identify instructions that reference these global variables. This is shown in the image given below. Mimikatz scans for the sequence of bytes outlined in black below in order to identify the mov r9d, cs:?LogonSessionListCount and lea rcx, ?LogonSessionList instructions. On the x86_64 architecture, these instructions use rip relative addressing to access variables relative to the value of the current instruction pointer using a given offset. These offsets are outlined in blue and green in little-endian notation:
The diagram given above is explained in more depth below:
In the following example, we calculate the address of LogonSessionList and LogonSessionListCount using the information obtained using this signature. The offset from rip is 0x107E3A and the address of rip is marked using an orange line (0x80065926). Adding these, we deduce that the address of LogonSessionListCount is 0x8016D760. We can verify this manually using IDA Pro as seen below:
In this instance, the Mimikatz developers used the WLsaEnumerateLogonSession function for their signature. However, if we examine LsaSrv.dll in IDA Pro, we can see several other candidate functions:
In this section, we will describe how Mimikatz pivots from LogonSessionList and LogonSessionListCount to overwrite the authentication information associated with the targeted logon session. This is done in the kuhl_m_sekurlsa_enum function, which accepts a callback function that is invoked when traversing the list of cached credential items.
This can be seen in the code given below from kuhl_m_sekurlsa_pth_luid. This uses the kuhl_m_sekurlsa_enum function to specify two callbacks (one for each authentication provider: MSV1_0 and Kerberos). Specifically, these are kuhl_m_sekurlsa_enum_callback_msv_pth and kuhl_m_sekurlsa_enum_callback_kerberos_pth.
When the kuhl_m_sekurlsa_enum function is invoked, it will loop through all of the entries in LogonSessionList using LogonSessionCount to determine the number of entries. Each of the entries themselves is a pointer to a LIST_ENTRY linked list containing one or more structures of type _KIWI_BASIC_SECURITY_LOGON_SESSION_DATA. The MSV1_0 callback is invoked and receives a copy of the structure along with a PSEKURLSA_PTH_DATA structure containing relevant information such as the target LogonId and the NTLM hash that is being used for the attack.
The purpose of this check is to verify that the correct LogonId has been found. A second function is invoked which handles the primary logic for implementing the pass-the-hash technique. This function (kuhl_m_sekurlsa_msv_enum_cred) invokes kuhl_m_sekurlsa_msv_enum_cred_callback_pth. Next, this function receives a structure of type KIWI_MSV1_0_PRIMARY_CREDENTIAL. This structure appears to be undocumented and was probably obtained through reverse engineering.
The following modifications are made to the credential structure:
The primary work modifying the Kerberos structures in-memory is the kuhl_m_sekurlsa_enum_generic_callback_kerberos function, which is responsible for parsing either a linked list (prior to Windows Vista) or AVL tree, to identify the Kerberos structures related to the targeted logon session. The callback function kuhl_m_sekurlsa_enum_kerberos_callback_pth is then executed, which is responsible for patching in the appropriate credential information.
The technique leveraged by Mimikatz is conceptually simple, but the implementation is subtle and complex. The tool simply identifies credential structures in-memory and overwrites them with the appropriate credential material specified by the attacker. That being said, extensive effort has been put into reverse engineering these internal structures along with building out a set of signatures and other heuristics for each major operating system version.
Overwriting these structures does not change the security information or user information for the local user account. The credentials stored in LSASS are associated with the logon session used for network authentication and not for identifying the local user account associated with a process.
Widespread attacker usage of Windows credential dumping means that suspicious access to the LSASS process is heavily monitored in most environments with a mature security program. In this section, we cover alternative techniques that can be leveraged within an environment that have a lower probability of detection:
Another technique would be for the operator to develop a custom device driver used to read to and write to LSASS process memory. Depending on how this driver is implemented, it would likely evade the standard methods used to detect LSASS process memory accesses.
In some ways, effective red teaming often involves “reinventing the wheel” or finding creative ways to accomplish the same basic task. Different techniques often have a different probability of detection, and understanding the implementation of a given technique allows you to create custom variations with a lower likelihood of detection. The likelihood of detection is typically a product of:
In this post, we covered the method used by Mimikatz to implement pass-the-hash and compared it with potential alternative implementations. This will allow the reader to implement their own customized versions of Mimikatz (or smaller utilities with their own implementations).
Modifying LSASS memory to leverage compromised NTLM credentials is only one method among many. It is important to remember that there are a variety of other methods that can be used to leverage compromised NTLM credentials if one wishes to avoid potentially suspicious interactions with LSASS. For instance, the Rubeus tool provides a mechanism that can be used to request a TGT using a user’s NTLM hash. The TGT can then be imported into LSASS by connecting to the LSA over ALPC and interacting with the Kerberos authentication package [3].
In addition to this, if the implant being used (such as Cobalt Strike’s beacon) supports SOCKS proxying, it is possible to use Impacket to communicate directly with internal systems to request a TGT through Kerberos, or use the NTLM hash to authenticate [2]. Depending on the controls present in the target environment, these techniques may be stealthier than writing to LSASS process memory.
[1]https://www.ampliasecurity.com/research/WCE_Internals_RootedCon2011_ampliasecurity.pdf
[2]https://www.harmj0y.net/blog/redteaming/from-kekeo-to-rubeus/
[3]https://github.com/GhostPack/Rubeus/blob/a3ebc3a30d2b3e7c61ed51c5d5665b9b15379f98/Rubeus/lib/LSA.cs
[4]https://www.coresecurity.com/sites/default/private-files/publications/2016/05/Ochoa_2008-Pass-The-Hash.pdf
[5]https://recon.cx/2008/a/thomas_garnier/LPC-ALPC-slides.pdf
[6]https://github.com/SecureAuthCorp/impacket