HOWTO: Implement your own NtOpenProcess in kernel-mode

Writing a system utility but annoyed by the fact that you can’t open the processes of security software and rootkits, instead receiving “Access Denied” errors? This is commonly due to security software and rootkits hooking ZwOpenProcess. There are many ways they can do this: SSDT modification, inline hooking (detours), sysenter hooks, etc. There is a simple way of bypassing all of these methods: you can implement NtOpenProcess yourself (in a driver). Let’s go through the steps.

Firstly, we should declare some functions we’ll be using:

#include <ntifs.h>

NTKERNELAPI NTSTATUS NTAPI SeCreateAccessState(
    PACCESS_STATE AccessState,
    PVOID AuxData,
    ACCESS_MASK DesiredAccess,
    PGENERIC_MAPPING Mapping
    );

NTKERNELAPI VOID NTAPI SeDeleteAccessState(
    PACCESS_STATE AccessState
    );

Our custom NtOpenProcess will be called MyNtOpenProcess, and will be equivalent to the OpenProcess Windows API function. It will not perform any memory probing – you can’t call it from user-mode anyway. However, you can use this function to create a user-mode handle which can be used in user-mode.

NTSTATUS MyNtOpenProcess(
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    HANDLE ProcessId,
    KPROCESSOR_MODE AccessMode /* specify UserMode if you want the handle to be usable in user-mode */
    )
{
    /* Some local variables we'll be using */
    NTSTATUS status = STATUS_SUCCESS;
    ACCESS_STATE accessState;
    /* Some internal structure Windows uses - we don't need to know about it */
    char auxData[0xc8];
    PEPROCESS processObject = NULL;
    HANDLE processHandle = NULL;

Firstly, we need to create an access state:

status = SeCreateAccessState(
    &accessState,
    auxData,
    DesiredAccess,
    /* Highly OS-dependent code - the "52" was taken from a Vista SP1 kernel */
    (PGENERIC_MAPPING)((PCHAR)*PsProcessType + 52)
    );

if (!NT_SUCCESS(status))
    return status;

Now we grant whatever access to the process the caller wants, regardless of object permissions:

accessState.PreviouslyGrantedAccess |= accessState.RemainingDesiredAccess;
accessState.RemainingDesiredAccess = 0;

Now we get a pointer to the EPROCESS structure for the process:

status = PsLookupProcessByProcessId(ProcessId, &processObject);

if (!NT_SUCCESS(status))
{
    SeDeleteAccessState(&accessState);
    return status;
}

Finally, we open a handle to the process object:

status = ObOpenObjectByPointer(
    processObject,
    0,
    &accessState,
    0,
    *PsProcessType,
    AccessMode,
    &processHandle
    );

/* We don't need these two things anymore: */
SeDeleteAccessState(&accessState);
ObDereferenceObject(processObject);

/* If the handle was opened, we give it to our caller. */
if (NT_SUCCESS(status))
    *ProcessHandle = processHandle;

return status;

It’s pretty simple. Here’s the entire function:

NTSTATUS MyNtOpenProcess(
    PHANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    HANDLE ProcessId,
    KPROCESSOR_MODE AccessMode
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    ACCESS_STATE accessState;
    char auxData[0xc8];
    PEPROCESS processObject = NULL;
    HANDLE processHandle = NULL;
    
    status = SeCreateAccessState(
        &accessState,
        auxData,
        DesiredAccess,
        (PGENERIC_MAPPING)((PCHAR)*PsProcessType + 52)
        );

    if (!NT_SUCCESS(status))
        return status;
    
    accessState.PreviouslyGrantedAccess |= accessState.RemainingDesiredAccess;
    accessState.RemainingDesiredAccess = 0;
    
    status = PsLookupProcessByProcessId(ProcessId, &processObject);
    
    if (!NT_SUCCESS(status))
    {
        SeDeleteAccessState(&accessState);
        return status;
    }
    
    status = ObOpenObjectByPointer(
        processObject,
        0,
        &accessState,
        0,
        *PsProcessType,
        AccessMode,
        &processHandle
        );
    
    SeDeleteAccessState(&accessState);
    ObDereferenceObject(processObject);
    
    if (NT_SUCCESS(status))
        *ProcessHandle = processHandle;
    
    return status;
}

Notes:

  • This same technique can be applied to any type of object: if you can get a pointer to a kernel object, you can use ObOpenObjectByPointer to open it as a handle.
  • This technique is vulnerable to inline hooks on ObOpenObjectByPointer. There is nothing you can do about this except read the function from disk and apply relocations as necessary.
  • The code I have supplied has not been tested. Use at your own risk.

2 responses

Leave a Reply