Process

The first group of modules we’ll really dig into is the Process group, which allows for interaction and modification of user mode processes. Because we will be working with processes in this section, it is important to understand what they look like from the kernel’s perspective. Processes in the kernel center around the EPROCESS structure, an opaque structure that serves as the object for a process. Inside of the structure are all of the attributes of a process that we are familiar with, such as the process ID, token information, and process environment block (PEB).

EPROCESS structures in the kernel are connected through a circular doubly-linked list. The list head is stored in the kernel variable PsActiveProcessHead and is used as the “beginning” of the list. Each EPROCESS structure contains a member, ActiveProcessLinks , of the type LIST_ENTRY . The LIST_ENTRY structure has 2 components — a forward link ( Flink ) and a backward link ( Blink ). The Flink points to the Flink of the next EPROCESS structure in the list. The Blink points to the Flink of the previous EPROCESS structure in the list. The Flink of the last structure in the list points to the Flink of PsActiveProcessHead . This creates a loop of EPROCESS structures and is represented in this simplified graphic.

!process

The first module gives us a list of processes running on the system, along with some additional information about them. This works by walking the linked list described earlier using 2 Windows version-specific offsets — EprocessNext and EprocessFlags2 . EprocessNext is the offset in the current EPROCESS structure containing the address of the ActiveProcessLinks member, where the Flink to the next process can be read (e.g. 0x02f0 in Windows 10 1903). EProcessFlags2 is a second set of ULONG bitfields introduced in Windows Vista, hence why this is only shown when running on systems Vista and above, used to give use some more detail. Specifically:

PrimaryTokenFrozen — Uses a ternary to return “F-Tok” if the primary token is frozen and nothing if it isn’t. If PrimaryTokenFrozen is not set, we can swap in our token such as in the case of suspended processes. In a vast majority of cases, you will find that the primary token is frozen.

— Uses a ternary to return “F-Tok” if the primary token is frozen and nothing if it isn’t. If is not set, we can swap in our token such as in the case of suspended processes. In a vast majority of cases, you will find that the primary token is frozen. SignatureProtect — This is actually 2 values - SignatureLevel and SectionSignatureLevel . SignatureLevel defines the signature requirements of the primary module. SectionSignatureLevel defines the minimum signature level requirements of a DLL to be loaded into the process.

— This is actually 2 values - and . defines the signature requirements of the primary module. defines the minimum signature level requirements of a DLL to be loaded into the process. Protection — These 3 values, Type , Audit , and Signer , are members of the PS_PROTECTION structure which represent the process’ protection status. Most important of these is Type , which maps to the following statuses, which you may recognize as PP/PPL:

!processProtect

The !processProtect function is one of, if not the most, used functionalities supplied by Mimidrv. Its objective is to add or remove process protection from a process, most commonly LSASS. The way it goes about modifying the protection status is relatively simple:

Use nt!PsLookupProcessByProcessId to get a handle on a process’ EPROCESS structure by its PID. Go to the version-specific offset of SignatureProtect in the EPROCESS structure. Patches 5 values — SignatureLevel , SectionSignatureLevel , Type , Audit , and Signer (the last 3 being members of the PS_PROTECTION struct) — depending on whether or not it is protecting or unprotecting the process. If protecting, the values will be 0x3f, 0x3f, 2, 0, 6 , representing a protected signer of WinTcb and protection level of Max . If unprotecting, the values will be 0, 0, 0, 0, 0 , representing an unprotected process. Finally, dereference the EPROCESS object.

This module is particularly relevant for us as attackers because most obviously we can remove protection from LSASS in order to extract credentials, but more interestingly we can protect an arbitrary process and use that to get a handle on another protected process. For example, we use !processProtect to protect our running mimikatz.exe and then run some command to extract credentials from LSASS and it should work despite LSASS being protected. An example of this use case is shown below.

!processToken

Continuing with another operationally-relevant function is !processToken which can be used to duplicate a process token and pass it to an attacker-specified process. This is most commonly used during DCShadow attacks and is similar to token::elevate , but modifies the process token instead of the thread token.

With no arguments passed, this function will grant all cmd.exe , powershell.exe , and mimikatz.exe processes a NT AUTHORITY\SYSTEM token. Alternatively, it takes “to” and “from” parameters which can be used to define the process you wish to copy the token from and process you want to copy it to.

To duplicate the token, Mimikatz first sets the “to” and “from” PIDs to the user-supplied values, or “0” if not set, and then places them in a MIMIDRV_PROCESS_TOKEN_FROM_TO struct, which sent to Mimidrv via IOCTL_MIMIDRV_PROCESS_TOKEN .

Once Mimidrv receives the PIDs specified by the user, it gets handles on the “to” and “from” processes using nt!PsLookupProcessByProcessId . If it was able to get a handle on those processes, it uses nt!ObOpenObjectByPointer to get a kernel handle ( OBJ_KERNEL_HANDLE ) on the “from” process. This is required by the following call to nt!ZwOpenProcessTokenEx , which will return a handle on the “from” process’ token.

At this point, the logic forks somewhat. In the first case where the user has supplied their own “to” process, Mimidrv calls kkll_m_process_token_toProcess . This function first uses nt!ObOpenObjectByPointer to get a kernel handle on the “to” process. Then it calls ZwDuplicateToken to get the token from the “from” process and stash it in an undocumented PROCESS_ACCESS_TOKEN struct as the Token attribute. If the system is running Windows Vista or above, it sets PrimaryTokenFrozen (described in the !process section) and then calls the undocumented nt!ZwSetInformationProcess function to do the actual work of giving the duplicated token to the “to” process. Once that completes, it cleans up by closing the handles to the “to” process and PROCESS_ACCESS_TOKEN struct.

In the event that no “to” process was specified, Mimidrv leverages the kkll_m_process_enum function used in !process to walk the list of processes on the system. Instead of using the kkll_m_process_list_callback callback, it uses kkll_m_process_systoken_callback , which uses ntdll!RtlCompareMemory to check if the ImageFileName matches “mimikatz.exe”, “cmd.exe”, or “powershell.exe”. If it does, it passes a handle to that process to kkll_m_process_token_toProcess and the functionality described in the paragraph before this is used to grant a duplicated token to that process, and then it continues walking the linked list looking for other matches.

!processPrivilege

This is a relatively simple function that grants all privileges (e.g. SeDebugPrivilege , SeLoadDriverPrivilege ), but includes some interesting code that highlights the power of operating in ring 0. Before we jump into exactly how Mimidrv modifies the target process token, it is important to understand what a token looks like in the kernel.

As discussed earlier, the EPROCESS structure contains attributes of a process, including the token (offset 0x360 in Windows 10 1903). You may notice that the token of the type EX_FAST_REF rather than TOKEN .

This is some internal Windows weirdness, but these pointers are built around that fact that that kernel structures are aligned on a 16-byte boundary on x64 systems. Due to this alignment, spare bits in the pointer are available to be used for reference counting. Where this becomes relevant for us is that the last 1 byte of the pointer will be the reference to our object — in this case a pointer to the TOKEN structure.

To demonstrate this practically, let’s hunt down the token of the System process in WinDbg. First, we get the address of the EPROCESS structure for the process.

Because we know that the token EX_FAST_REF will be at offset 0x360 , we can use WinDbg’s calculator to do some quick math and give us the memory address at the result of the equation.

Now that we have the address of the EX_FAST_REF , we can change the last byte to 0 to get the address of our TOKEN structure, which we’ll examine with the !token extension.

So now that we can identify the TOKEN structure, we can examine some of its attributes.

Most relevant to !processPrivileges is the Privileges attribute (offset 0x40 on Vista and above). This attribute is of the type SEP_TOKEN_PRIVILEGES which contains 3 attributes — Present , Enabled , and EnabledByDefault . These are bitmasks representing the token permissions we are used to seeing ( SeDebugPrivilege , SeLoadDriverPrivilege , etc.).

If we examine the function called by Mimidrv when we issue the !processPrivileges command, we can see that these bitmasks are being overwritten to enable all privileges on the primary token of the target process. Here’s what the result looks like in the GUI.