Process Protection
Last updated
Last updated
Protected Processes were first introduced in Windows Vista - not for security, but DRM (Digital Rights Management). The idea was to allow media players to read Blu-rays, but not copy the content. It worked fundamentally by limiting the access you could obtain to a protected process, such as PROCESS_QUERY_LIMITED_INFORMATION or PROCESS_TERMINATE, but not PROCESS_VM_READ or anything else that would allow you to circumvent the DRM requirements.
Tools such as can display the protection level of a process.
Since Vista, Protected Processes have been expanded. Instead of it simply being on or off, there are now hierarchical levels. First - there are two possible types, Protected Process (PP) and Protected Process Light (PPL). Second - there is also a Signer, which comes from the Extended Key Usage field of the digital signature used to sign the executable.
If we look at lsass.exe for example, we can see the certificate used to sign it has PPL verification enabled in the EKU.
Because of these various moving parts, there is an order of protection precedence that the kernel considers. PP always trumps PPL so a PPL can never obtain full access to a PP, regardless of its signer. A PP can gain full access to another PP or PPL if the signer is equal or greater, and a PPL can gain full access to another PPL if the signer is equal to or greater.
This makes sense because even though you want to protect a process like LSASS, other (more privileged) services still need access to it for it to function correctly.
typedef struct _EPROCESS *PEPROCESS;
The reason we call this "opaque" is because MS have not defined (or publicly documented) any of its members, which means we can't do something simple like eProcess->Protection
. Instead, we have to use static offsets obtained from WinDbg using the dt
command. Here we can see that Protection
is at offset 0x87a
.
We can also get the definition for _PS_PROTECTION
.
To make things a little easier for ourselves, we can define these in driver.h
.
We also want a structure that will allow the client to provide a target process ID.
Under a new IOCTL, MY_DRIVER_IOCTL_UNPROTECT_PROCESS
, we will cast the input buffer.
We then need to blindly cast the memory at the static offset to our PS_PROTECTION
structure.
We can now set all of the protection fields to zero, which will disable the protection.
In the client, we can parse a PID from the command line using atoi
.
Close and reopen Process Explorer to see that LSASS is no longer protected.
Mimikatz can now work.
Remember that change is only in memory. When the machine is rebooted, LSASS will start and its protection will be reapplied.
We may also protect any arbitrary process in much the same way. The in-memory values will already be all 0, so we can just set them to whatever we want (as long as they're valid). WinDbg can be used to get the process protection values of any process, which is useful for mimicking a legitimate process.
For example, to copy the values from SgrmBroker.exe, begin by grabbing the address of it's EPROCESS structure using the !process
command.
We know the offset to the PS_PROCESS_PROTECTION struct is +0x87a, so we can read memory at the target address and display it as the correct data type.
The values displayed here are in decimal. The type, 0y010
, is decimal 2, which matches the PS_PROTECTED_TYPE for PsProtectedTypeProtected
.
The signer, 0y0110
, is decimal 6, which matches the PS_PROTECTED_SIGNER for PsProtectedSignerWinTcb
.
To mimic the above in our driver, we could do:
As per the protected process hierarchy, we can access lsass.exe because mimikatz.exe is now running at a higher level.
I'm writing this course on Windows 10 LTSC, 21H2, build number 19044, so those are the values I will check for.
The impact of this is most notable when to LSASS as we cannot extract credential material from it, even when running as SYSTEM. We get access denied when trying to obtain a handle with enough privileges to query and read its memory. This is not an AV or EDR protection - simply the Windows kernel.
The PS_PROTECTED_SIGNER
struct provides a view of the possible signers that can be used.
From the perspective of the kernel, the protection level of a process is stored in a structure called . This is an opaque structure which is defined in wdm.h
as:
And then call to obtain a pointer to the process' EPROCESS structure.
An important aspect to note here is that the output of PsLookupProcessByProcessId is reference counted. This count is used by the kernel to track callers that have references to objects in memory to prevent them from being disposed whilst still in use. Whenever we are finished with a reference-counted object, we must call . Failing to do so will cause kernel memory leaks. In this particular example, if the target process were to be closed, the kernel will never free its EPROCESS structure from memory, because we will still have a reference to it.
We have currently hardcoded our static offset value but if this was run on a different version of Windows, it very likely cause a BSOD. One way to guard against this is to call when the driver is loaded and return an error if running on an untested version.