Introduction

Some time ago on a lazy, snowy sunday afternoon I decided to take a deep dive into mimikatz. While browsing through the features of this fascinating tool I came across the module lsadump::lsa and just started to explore that. Since mimikatz's overall documentation is ... improvable ;) I decided to write my findings down. Maybe this helps somebody.

The module lsadump::lsa includes two commands, which I will explore in the following: /patch and /inject. Both commands operate on the SamSs service with the goal to retrieve credentials. Both commands begin their work by acquiring a handle on the SamSs service (lsass.exe). The handle is acquired by calling the syscall OpenProcess() with the flags which are required to execute the specified command. The /patch command, for example, requests the following permissions on lsass.exe:

PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION

/inject is additionally requesting PROCESS_CREATE_THREAD, which /patch does not require, since it does not start a thread inside lsass.exe.

hint: these flags are a great place to start hunting using sysmon with your Blue Team).

Both commands let you limit which user's credentials get retrieved, either by specifying his RID (/id:500) or his name (/name:administrator). Before the magic starts mimikatz follows this path for both commands:

First mimikatz opens a handle on the LSA policy (LsaOpenPolicy()), using this handle it retrieves the domain information (LsaQueryInformationPolicy()). Then, for both commands, it connects to the SAM API (SamConnect()). There it opens the found domain (SamOpenDomain()). After that RID and username handles can be retrieved to limit the scope of the credential retrival as specified by the user.

The next step is to retrive the credentials. How does mimikatz do that?

/patch

mimikatz lsadump::lsa /patch

As the command name suggests mimikatz is patching something to dump the NTLM hashes - namely the samsrv.dll running inside the process lsass.exe.

The relevant function (kuhl_m_lsadump_lsa())is defined in modules/kuhl_m_lsadump.c. The following code section shows just the information which is relevant for patching (my following example shows the Windows 8 x86 DLL for samsrv.dll):

BYTE PTRN_WALL_SampQueryInformationUserInternal[]   = {0xc6, 0x40, 0x22, 0x00, 0x8b};
BYTE PATC_WALL_JmpShort[]                           = {0xeb, 0x04};
{KULL_M_WIN_BUILD_8,        {sizeof(PTRN_WALL_SampQueryInformationUserInternal),    PTRN_WALL_SampQueryInformationUserInternal},    {sizeof(PATC_WALL_JmpShort),    PATC_WALL_JmpShort},    {-12}}

After retrieving some information (e.g. base address of samsrv.dll in memory) necessary to patch the memory of the library the function kull_m_patch() searches for the byte sequence "c6 40 22 00" in memory. After finding that it replaces the two bytes at the found address of the byte sequence "c6 40 22 00" minus 12 with the bytes "eb 04". So what does that mean? Let us look at the disassembly:

Disassembly of the patched location

The location marked by the red breakpoint is the instruction which gets replaced with the bytes "eb 04". "eb 04" is a unconditional short jump of 4 bytes - which has the effect that the access check on the value 0x20 is ignored and the flow directly jumps to the preparation for calling the SAM-internal function _SampRetrieveUserPasswords. It seems this function is populating the attributes of the requested object with the NTLM hashes.

By skimming over the disassembly (and by API the general API layout) I assume the register edi holds a handle to the UserObject, which whats to access the user's passwords. So what is referenced at edi+0xC? Whatever it is - if this byte's value is not 0x20 the call of _SampRetrieveUserPasswords is skipped.

Searching for a documenation of the UserObject passed in edi I found the ReactOS source code which shows that the object behind the handle is typedefed to _SAM_DB_OBJECT. By having a look at this object we can assume what edi+0xC is:

_SAM_DB_OBJECT

Since ULONG is 4 Bytes and SAM_DB_OBJECT_TYPE is an enum, which is an int and therefore also 4 Bytes 0xC means edi+0xC is accessing the ACCESS_MASK value - and since we are on x86 it's least significant byte. Lastly, by having a look at the SAMR documentation of the ACCESS_MASK, we can see: If the LSB of ACCESS_MASK is 0x20 the user accessing the object has the USER_WRITE_ACCOUNT permission, which translates to "Specifies the ability to write attributes related to the administration of the user object."

After patching SampQueryInformationUserInternal() mimikatz is calling

SamQueryInformationUser(hUser, UserInternal1Information, &pUserInfoBuffer);

to retrieve UserInternal1Information - which results, internally in samsrv.dll, in a call to SampQueryInformationUserInternal(), which we just patched.

UserInternal1Information is documented as follows:

typedef struct _SAMPR_USER_INTERNAL1_INFORMATION {
  ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
  ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
  unsigned char NtPasswordPresent;
  unsigned char LmPasswordPresent;
  unsigned char PasswordExpired;
} SAMPR_USER_INTERNAL1_INFORMATION

Just a additional note: You see the "case 0x12"in the disassembly above? SampQueryInformationUserInternal() is handling multiple internal buffers, and 0x12 stands for, as you can see here, for UserInternal1Information. SampQueryInformationUserInternal is just build as a huge switch-case-statement to handle the different types of requestable internal information buffers. IDA recognizes that switch-case-statement greatly!

/inject

After we understood the technique used by mimikatz for /patch, what happens when we call lsadump::lsa /inject? Inject essentially starts a thread in the context of lsass.exe (SamSs-Service) and dumps the requested credentials from within this thread.

The code for the started thread is defined here, let us just take a exemplary part of the code to analyze the inner workings of /inject:

mimikatz/modules/kuhl_m_lsadump_remote.c:
...
if(NT_SUCCESS(((PSAMICONNECT) 0x4141414141414141)(NULL, &hSam, 0x10000000, TRUE)))
{
    if(NT_SUCCESS(((PLSAIQUERYINFORMATIONPOLICYTRUSTED) 0x4848484848484848)(PolicyAccountDomainInformation, (PVOID *)(&pPolicyDomainInfo))))
    {
        if(NT_SUCCESS(((PSAMROPENDOMAIN) 0x4444444444444444)(hSam, 0x10000000, pPolicyDomainInfo->DomainSid, &hDomain)))
        {
            if(NT_SUCCESS(((PSAMROPENUSER) 0x4545454545454545)(hDomain, 0x10000000, lpParameter->input.inputDword, &hUser)))
...

mimikatz/modules/kuhl_m_lsadump_remote.h:
...
typedef NTSTATUS (WINAPI * PSAMICONNECT) (IN PUNICODE_STRING ServerName, OUT SAMPR_HANDLE * ServerHandle, IN ACCESS_MASK DesiredAccess, IN BOOLEAN Trusted);
...

This code is casting some strange values (0x4141...) and then executing this? How does that work? Under no cirumstance these values are valid memory addresses, which can be executed inside lsass.exe. Something else must be happening...

mimikatz/modules/kuhl_m_lsadump.c:
...
REMOTE_EXT extensions[] = {
    {szSamSrv,  "SamIConnect",                      (PVOID) 0x4141414141414141, NULL},
    {szSamSrv,  "SamrCloseHandle",                  (PVOID) 0x4242424242424242, NULL},
    {szSamSrv,  "SamIRetrievePrimaryCredentials",   (PVOID) 0x4343434343434343, NULL},
    {szSamSrv,  "SamrOpenDomain",                   (PVOID) 0x4444444444444444, NULL},
    {szSamSrv,  "SamrOpenUser",                     (PVOID) 0x4545454545454545, NULL},
    {szSamSrv,  "SamrQueryInformationUser",         (PVOID) 0x4646464646464646, NULL},
    {szSamSrv,  "SamIFree_SAMPR_USER_INFO_BUFFER",  (PVOID) 0x4747474747474747, NULL},
    {szLsaSrv,  "LsaIQueryInformationPolicyTrusted",(PVOID) 0x4848484848484848, NULL},
    {szLsaSrv,  "LsaIFree_LSAPR_POLICY_INFORMATION",(PVOID) 0x4949494949494949, NULL},
    {szKernel32,"VirtualAlloc",                     (PVOID) 0x4a4a4a4a4a4a4a4a, NULL},
    {szKernel32,"LocalFree",                        (PVOID) 0x4b4b4b4b4b4b4b4b, NULL},
    {szNtDll,   "memcpy",                           (PVOID) 0x4c4c4c4c4c4c4c4c, NULL},
};
...

Mimikatz executes followin steps to inject into lsass:

  1. take the allocated memory, in which the code for the remote thread resides(kuhl_sekurlsa_samsrv_thread())
  2. resolve addresses of the functions (listed in the extensions structure, see above) inside the lsass process (kull_m_remotelib_GetProcAddressMultipleModules())
  3. parse the created thread for the "marker" bytes (e.g. 0x414141...) and replace them with the real address of the associated samsrv.dll-functions, found in step 2. (kull_m_remotelib_CreateRemoteCodeWitthPatternReplace())
  4. run the thread and exploit!

Since mimikatz requires PROCESS_CREATE_THREAD in its OpenProcess() call for /inject, the overall requested flag mask is pretty unique (at least in my test environment)... as already mentioned above: sysmon to the Blue Team rescue! ;)

What a nice technique. With the /inject command mimikatz is additionally querying samsrv via the function SamIRetrievePrimaryCredentials to retrieve supplementalCredentials like WDigest and Kerberos Hashes. (see kn[][] in kuhl_m_lsadump_remote.c)

mimikatz lsadump::lsa /inject /id:1001

While digging around in SamIRetrieveMultiplePrimaryCredentials (which is directly called by SamIRetrievePrimaryCredentials, see below) some questions arised:

Disassembly of SamIRetrievePrimaryCredentials

  • what is at $esi+0x80? $esi is, following the disassembly, the UserObject which requests the credentials. I did not found any documentation which explains this memory location?
  • At least the second bit $esi+0x80 has to be set so that SampExtRetrieveMultiplePrimaryCredentials() is called. So we can assume this is the place where the granted access rights of the passed user object are checked. I do not see any obvious reason why this code can't be patched, to extend the /patch command to retrieve Kerberos and WDigest hashes? Any idea?

Anyhow, hopefully this post explains some things (or does at least rise some more good questions ;)) and did reach the right level between highlevel overview and deepdive. Personally I learned a lot of the inner workings of mimikatz and Windows by digging around the disassembly of samsrv.dll and the source code of mimikatz!

As always: Feedback is very welcome!