Credentials Dumping

Living-off-the-Land Binaries

rundll32.exe C:\Windows\System32\comsvcs.dll MiniDump PID lsass.dmp full

Globfuscation

&$env:???t??r???\*2\r[t-u]???[k-l]?2* $(gi $env:???t??r???\*2\c?m?[v-w]*l | % {
  $_.FullName }), `#-999999999999999999999999999999999999999999999999999999999999
  999999999999999999999999999999999999999999999999999999999999999999999999999999
  999999999999999999999999999999999999999999999999999999999999999999999999999999
  99999999999999999976-decoy $(gps l?a*s).id c:\t??p\dmp.log full;

MiniDumpWriteDump

To use the MiniDumpWriteDump API, we need a HANDLE to lsass.exe. Assuming we got such a handle, we do the following:

  1. Create a new file and get its handle.

  2. Invoke the MiniDumpWriteDump APIarrow-up-right on the lsass.exe handle and the file handle.

However, there is a variant that doesn't require writing to disk at all - we can save the entire dump to a memory buffer! To achieve that, we:

  1. Create a buffer with some initial size.

  2. Create a structure of type MINIDUMP_CALLBACK_INFORMATION and specify a callback function (and context).

  3. Invoke the MiniDumpWriteDump APIarrow-up-right on the lsass.exe handle and the callback pointer.

  4. The callback accumulates the data from the minidump argument upon receivng a IoWriteAllCallback callback type. Ths might require us to dynamically enlarge the buffer with the HeapReAlloc API.

There are two variants to get a handle to lsass.exe:

Using OpenProcess

This is the most direct and "normal" way of fetching the lsass.exe process handle:

  1. Finding the process ID of lsass.exe with CreateToolhelp32Snapshot APIarrow-up-right, as well as Process32FirstWarrow-up-right and Process32NextWarrow-up-right API calls.

  2. Use the OpenProcessarrow-up-right API. For doing a Minidump, we will require the PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access flags.

Stealing An Existing Handle

Since OpenProcess is usually heavily monitored by security products, there is a sneakier option - duplicating an existing handle to lsass.exe some other process might have.

Similarly to the OpenProcess approach - we fetch the lsass.exe PID the usual way (CreateToolhelp32Snapshot and friends).

However, here our approach would be finding an existing handle to lsass.exe with sufficient access flags:

  1. We call ntdll!NtQuerySystemInformationarrow-up-right with the information class SystemHandleInformation (defined as 16) with a sufficiently large buffer. As of now, that information class is officially undocumented (but can be found herearrow-up-right).

  2. We treat the buffer as a SYSTEM_HANDLE_INFORMATIONarrow-up-right pointer (again officially undocumented).

  3. For each handle, we skip if it belongs to the lsass.exe process ID itself, to our own process or the SYSTEM process ID (4).

  4. We call ntdll!NtDuplicateObject to duplicate the handle. In this call we also specify the required access flags.

  5. We call ntdll!NtQueryObject with type OBJECT_TYPE_INFORMATION_CLASS to get the duplicated handle type and make sure it's a process handle type.

  6. We call the QueryFullProcessImageNameW APIarrow-up-right and validate it's indeed lsass.exe (note it's a full name so extra parsing is required).

  7. If all checks pass - we got a handle to lsass.exe that we've just duplicated! Otherwise, we clean up used resources and try a different handle.

Note: a running OS is not guaranteed to have an open lsass.exe by some process with sufficient access flags - if that's the case we can always revert to running OpenProcess as usual. However, all the native APIs (and especially the handle duplication) make it way stealtier than calling OpenProcess directly.

PssCaptureSnapshot-based dumping

Similarly to the Minidump approach, we get a handle to lsass.exe by means of OpenProcess or handle duplication, but this time also with PROCESS_DUP_HANDLE access flag.

Then, we call the PssCaptureSnapshot APIarrow-up-right which returns us a pseudo-handle of type HPSS. As the name suggests, that handle captures a "snapshot" of the lsass.exe state (includng memory), and then could be used just like a normal handle to ReadProcessMemory or MiniDumpWriteDump.

The approach doesn't seem advantageous, but it's very reliable due to how it captures a snapshot (as opposed to direct ReadProcessMemory which has an inherent race condition, for example).

SilentProcessExit-based dumping

This technique relies on a Windows mechanism that can invoke a callback once a process exits, as well as fooling the system into thinking the process indeed exited. To implement this technique, we do the following:

  1. Get an lsass.exe handle (either via OpenProcess or by handle duplication).

  2. Write to the registry under the Image File Execution Options (IFEO)arrow-up-right key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe. Value name is GlobalFlag which is of type DWORD, and we set it to the value of 0x200, which corresponds to enabling the Silent Process Exit monitoring feature.

  3. Write to the registry again, this time under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe, the value is a DWORD with the name ReportingMode and the value of 2, which instructs the OS to perform a full memory dump.

  4. Set the value LocalDumpFolder under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe to point to a folder we'd like to dump the memory to, and set the value DumpType (which is a DWORD) to be 2 (local memory dump).

  5. Call ntdll!RtlReportSilentProcessExit, which is an undocumented function that would report that a silent process exit should occur for a given process handle (lsass.exe in our case).

At that point, our dump file has been created at the folder we assigned, and we can delete all added registry entries.

Shtinikering

Originally done by Deep Instinctarrow-up-right, this method requires running as SYSTEM (can be validated using the GetTokenInformation APIarrow-up-right if necessary).

Just as before, we fetch a handle to lsass.exe with either OpenProcess or duplication, and then dumps using Windows Error Reporting (WER)arrow-up-right!

To do this, we report an exception to WER via ALPCarrow-up-right:

  1. We get some thread of lsass.exe (can be done with the CreateToolhelp32Snapshot API) - and save its thread ID.

  2. We create two events that we will pass to the ALPC message: a "recovery event" and a "completion event".

  3. We create a memory-mapped file that will contain a structure that'd maintain exception information (requires writable memory). That structure contains information for WER, including the events that we created, as well as exception information, process ID, the failing thread ID and so on.

  4. We make sure WER service starts using Windows Notification Facility (WNF)arrow-up-right using ntdll!NtUpdateWnfStateData and ntdll!EtwEventWriteNoRegistration.

  5. We wait for WER to be available using ntdll!NtWaitForSingleObject on \KernelObjects\SystemErrorPortReady.

  6. We connect to ALPC port \WindowsErrorReportingServicePort via ntdll!NtAlpcConnectPort and then send to message via ntdll!NtAlpcSendWaitReceivePort.

  7. The dump will be under %LocalAppData%\CrashDumps - we take the freshest dump.

Last updated