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 structure has 2 components â a forward link (
Flink) and a backward link (
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
PsActiveProcessHead. This creates a loop of
EPROCESS structures and is represented in this simplified graphic.
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 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
PrimaryTokenFrozenis 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 –
SignatureLeveldefines the signature requirements of the primary module.
SectionSignatureLeveldefines the minimum signature level requirements of a DLL to be loaded into the process.
Protectionâ These 3 values,
Signer, are members of the
PS_PROTECTIONstructure 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 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:
nt!PsLookupProcessByProcessIdto get a handle on a processâ
EPROCESSstructure by its PID.
- Go to the version-specific offset of
- Patches 5 values â
Signer(the last 3 being members of the
PS_PROTECTIONstruct) â 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
WinTcband protection level of
- If unprotecting, the values will be
0, 0, 0, 0, 0, representing an unprotected process.
- Finally, dereference the
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.
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
mimikatz.exe processes a
NT AUTHORITYSYSTEM 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
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
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.
This is a relatively simple function that grants all privileges (e.g.
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
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
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
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 â
EnabledByDefault. These are bitmasks representing the token permissions we are used to seeing (
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.
And here it is in the debugger while inspecting the memory at the
This is a syndicated post. Read the original post at Source link .