Evil UniqueProcessId

Completely hiding a process on a modern Windows system is nearly impossible, not only because of the many detection vectors but also because of PatchGuard protecting most important structures.

However, there is one field in the EPROCESS struct that is not protected, and it is the UniqueProcessId. While it might not seem that interesting at first glance, there are a few funny things you can do with it.

Stealing ID of other process

While I would not recommend this, since it can cause system instability (even when you restore it afterwards), it is possible to change the process ID to match another process. If this process was created before our target process (and therefore is first after the system process in the EPROCESS linked list), any call to OpenProcess or similar will result in a handle being opened for the process we stole the ID from, and not our target process, therefore “hiding” our target process.

PEPROCESS process;
NTSTATUS status = PsLookupProcessByProcessId((HANDLE)1234, &process);
if (!NT_SUCCESS(status))
    return status;

// EPROCESS->UniqueProcessId
*(PULONG64)((PBYTE)process + 0x440) = 6152; // PID of OneDrive

screenshot

Using 64-bit ID

Most user-mode APIs like OpenProcess, Process32First, Process32Next, and GetProcessId only allow you to specify a 32-bit integer as a process ID or will strip the higher 32 bits from their return values. This means that if you set the process ID to a 64-bit integer, the kernel will handle it just fine, but user-mode APIs will start failing.

PEPROCESS process;
NTSTATUS status = PsLookupProcessByProcessId((HANDLE)1234, &process);
if (!NT_SUCCESS(status))
    return status;

// EPROCESS->UniqueProcessId
*(PULONG64)((PBYTE)process + 0x440) = 0xFFFFFFFFFFFFFFFF;

screenshot