/*
 * KProcessHacker
 *
 * Copyright (C) 2010-2016 wj32
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <kph.h>
#include <dyndata.h>

#ifdef _X86_
#define KERNEL_HANDLE_BIT (0x80000000)
#else
#define KERNEL_HANDLE_BIT (0xffffffff80000000)
#endif

#define IsKernelHandle(Handle) ((LONG_PTR)(Handle) < 0)
#define MakeKernelHandle(Handle) ((HANDLE)((ULONG_PTR)(Handle) | KERNEL_HANDLE_BIT))

typedef struct _KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT
{
    PVOID Buffer;
    PVOID BufferLimit;
    PVOID CurrentEntry;
    ULONG Count;
    NTSTATUS Status;
} KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT, *PKPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT;

BOOLEAN KphpEnumerateProcessHandlesEnumCallback61(
    __inout PHANDLE_TABLE_ENTRY HandleTableEntry,
    __in HANDLE Handle,
    __in PVOID Context
    );

BOOLEAN KphpEnumerateProcessHandlesEnumCallback(
    __in PHANDLE_TABLE HandleTable,
    __inout PHANDLE_TABLE_ENTRY HandleTableEntry,
    __in HANDLE Handle,
    __in PVOID Context
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, KphReferenceProcessHandleTable)
#pragma alloc_text(PAGE, KphDereferenceProcessHandleTable)
#pragma alloc_text(PAGE, KphUnlockHandleTableEntry)
#pragma alloc_text(PAGE, KphpEnumerateProcessHandlesEnumCallback61)
#pragma alloc_text(PAGE, KphpEnumerateProcessHandlesEnumCallback)
#pragma alloc_text(PAGE, KpiEnumerateProcessHandles)
#pragma alloc_text(PAGE, KphQueryNameObject)
#pragma alloc_text(PAGE, KphQueryNameFileObject)
#pragma alloc_text(PAGE, KpiQueryInformationObject)
#pragma alloc_text(PAGE, KpiSetInformationObject)
#pragma alloc_text(PAGE, KphOpenNamedObject)
#endif

/**
 * Gets a pointer to the handle table of a process.
 *
 * \param Process A process object.
 *
 * \return A pointer to the handle table, or NULL if the process is terminating or the request is
 * not supported. You must call KphDereferenceProcessHandleTable() when the handle table is no
 * longer needed.
 */
PHANDLE_TABLE KphReferenceProcessHandleTable(
    __in PEPROCESS Process
    )
{
    PHANDLE_TABLE handleTable = NULL;

    PAGED_CODE();

    // Fail if we don't have an offset.
    if (KphDynEpObjectTable == -1)
        return NULL;

    // Prevent the process from terminating and get its handle table.
    if (NT_SUCCESS(PsAcquireProcessExitSynchronization(Process)))
    {
        handleTable = *(PHANDLE_TABLE *)((ULONG_PTR)Process + KphDynEpObjectTable);

        if (!handleTable)
            PsReleaseProcessExitSynchronization(Process);
    }

    return handleTable;
}

/**
 * Dereferences the handle table of a process.
 *
 * \param Process A process object.
 */
VOID KphDereferenceProcessHandleTable(
    __in PEPROCESS Process
    )
{
    PAGED_CODE();

    PsReleaseProcessExitSynchronization(Process);
}

VOID KphUnlockHandleTableEntry(
    __in PHANDLE_TABLE HandleTable,
    __in PHANDLE_TABLE_ENTRY HandleTableEntry
    )
{
    PEX_PUSH_LOCK handleContentionEvent;

    PAGED_CODE();

    // Set the unlocked bit.

#ifdef _M_X64
    InterlockedExchangeAdd64(&HandleTableEntry->Value, 1);
#else
    InterlockedExchangeAdd(&HandleTableEntry->Value, 1);
#endif

    // Allow waiters to wake up.

    handleContentionEvent = (PEX_PUSH_LOCK)((ULONG_PTR)HandleTable + KphDynHtHandleContentionEvent);

    if (*(PULONG_PTR)handleContentionEvent != 0)
        ExfUnblockPushLock(handleContentionEvent, NULL);
}

BOOLEAN KphpEnumerateProcessHandlesEnumCallback61(
    __inout PHANDLE_TABLE_ENTRY HandleTableEntry,
    __in HANDLE Handle,
    __in PVOID Context
    )
{
    PKPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT context = Context;
    KPH_PROCESS_HANDLE handleInfo;
    POBJECT_HEADER objectHeader;
    POBJECT_TYPE objectType;
    PKPH_PROCESS_HANDLE entryInBuffer;

    PAGED_CODE();

    objectHeader = ObpDecodeObject(HandleTableEntry->Object);
    handleInfo.Handle = Handle;
    handleInfo.Object = objectHeader ? &objectHeader->Body : NULL;
    handleInfo.GrantedAccess = ObpDecodeGrantedAccess(HandleTableEntry->GrantedAccess);
    handleInfo.ObjectTypeIndex = -1;
    handleInfo.Reserved1 = 0;
    handleInfo.HandleAttributes = ObpGetHandleAttributes(HandleTableEntry);
    handleInfo.Reserved2 = 0;

    if (handleInfo.Object)
    {
        objectType = ObGetObjectType(handleInfo.Object);

        if (objectType && KphDynOtIndex != -1)
            handleInfo.ObjectTypeIndex = (USHORT)*(PUCHAR)((ULONG_PTR)objectType + KphDynOtIndex);
    }

    // Advance the current entry pointer regardless of whether the information will be written; this
    // will allow the parent function to report the correct return length.
    entryInBuffer = context->CurrentEntry;
    context->CurrentEntry = (PVOID)((ULONG_PTR)context->CurrentEntry + sizeof(KPH_PROCESS_HANDLE));
    context->Count++;

    // Only write if we have not exceeded the buffer length. Also check for a potential overflow (if
    // the process has an extremely large number of handles, the buffer pointer may wrap).
    if (
        (ULONG_PTR)entryInBuffer >= (ULONG_PTR)context->Buffer &&
        (ULONG_PTR)entryInBuffer + sizeof(KPH_PROCESS_HANDLE) <= (ULONG_PTR)context->BufferLimit
        )
    {
        __try
        {
            *entryInBuffer = handleInfo;
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            // Report an error.
            if (context->Status == STATUS_SUCCESS)
                context->Status = GetExceptionCode();
        }
    }
    else
    {
        // Report that the buffer is too small.
        if (context->Status == STATUS_SUCCESS)
            context->Status = STATUS_BUFFER_TOO_SMALL;
    }

    return FALSE;
}

BOOLEAN KphpEnumerateProcessHandlesEnumCallback(
    __in PHANDLE_TABLE HandleTable,
    __inout PHANDLE_TABLE_ENTRY HandleTableEntry,
    __in HANDLE Handle,
    __in PVOID Context
    )
{
    BOOLEAN result;

    PAGED_CODE();

    result = KphpEnumerateProcessHandlesEnumCallback61(HandleTableEntry, Handle, Context);
    KphUnlockHandleTableEntry(HandleTable, HandleTableEntry);

    return result;
}

/**
 * Enumerates the handles of a process.
 *
 * \param ProcessHandle A handle to a process.
 * \param Buffer The buffer in which the handle information will be stored.
 * \param BufferLength The number of bytes available in \a Buffer.
 * \param ReturnLength A variable which receives the number of bytes required to be available in
 * \a Buffer.
 * \param AccessMode The mode in which to perform access checks.
 */
NTSTATUS KpiEnumerateProcessHandles(
    __in HANDLE ProcessHandle,
    __out_bcount(BufferLength) PVOID Buffer,
    __in_opt ULONG BufferLength,
    __out_opt PULONG ReturnLength,
    __in KPROCESSOR_MODE AccessMode
    )
{
    NTSTATUS status;
    BOOLEAN result;
    PEPROCESS process;
    PHANDLE_TABLE handleTable;
    KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT context;

    PAGED_CODE();

    if (KphDynNtVersion >= PHNT_WIN8 && KphDynHtHandleContentionEvent == -1)
    {
        return STATUS_NOT_SUPPORTED;
    }

    if (AccessMode != KernelMode)
    {
        __try
        {
            ProbeForWrite(Buffer, BufferLength, sizeof(ULONG));

            if (ReturnLength)
                ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG));
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            return GetExceptionCode();
        }
    }

    // Reference the process object.
    status = ObReferenceObjectByHandle(
        ProcessHandle,
        0,
        *PsProcessType,
        AccessMode,
        &process,
        NULL
        );

    if (!NT_SUCCESS(status))
        return status;

    // Get its handle table.
    handleTable = KphReferenceProcessHandleTable(process);

    if (!handleTable)
    {
        ObDereferenceObject(process);
        return STATUS_UNSUCCESSFUL;
    }

    // Initialize the enumeration context.
    context.Buffer = Buffer;
    context.BufferLimit = (PVOID)((ULONG_PTR)Buffer + BufferLength);
    context.CurrentEntry = ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->Handles;
    context.Count = 0;
    context.Status = STATUS_SUCCESS;

    // Enumerate the handles.

    if (KphDynNtVersion >= PHNT_WIN8)
    {
        result = ExEnumHandleTable(
            handleTable,
            KphpEnumerateProcessHandlesEnumCallback,
            &context,
            NULL
            );
    }
    else
    {
        result = ExEnumHandleTable(
            handleTable,
            (PEX_ENUM_HANDLE_CALLBACK)KphpEnumerateProcessHandlesEnumCallback61,
            &context,
            NULL
            );
    }

    KphDereferenceProcessHandleTable(process);
    ObDereferenceObject(process);

    // Write the number of handles if we can.
    if (BufferLength >= FIELD_OFFSET(KPH_PROCESS_HANDLE_INFORMATION, Handles))
    {
        if (AccessMode != KernelMode)
        {
            __try
            {
                ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->HandleCount = context.Count;
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                return GetExceptionCode();
            }
        }
        else
        {
            ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->HandleCount = context.Count;
        }
    }

    // Supply the return length if the caller wanted it.
    if (ReturnLength)
    {
        ULONG returnLength;

        // Note: if the CurrentEntry pointer wrapped, this will give the wrong return length.
        returnLength = (ULONG)((ULONG_PTR)context.CurrentEntry - (ULONG_PTR)Buffer);

        if (AccessMode != KernelMode)
        {
            __try
            {
                *ReturnLength = returnLength;
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                return GetExceptionCode();
            }
        }
        else
        {
            *ReturnLength = returnLength;
        }
    }

    return context.Status;
}

/**
 * Queries the name of an object.
 *
 * \param Object A pointer to an object.
 * \param Buffer The buffer in which the object name will be stored.
 * \param BufferLength The number of bytes available in \a Buffer.
 * \param ReturnLength A variable which receives the number of bytes required to be available in
 * \a Buffer.
 */
NTSTATUS KphQueryNameObject(
    __in PVOID Object,
    __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer,
    __in ULONG BufferLength,
    __out PULONG ReturnLength
    )
{
    NTSTATUS status;
    POBJECT_TYPE objectType;

    PAGED_CODE();

    objectType = ObGetObjectType(Object);

    // Check if we are going to hang when querying the object, and use
    // the special file object query function if needed.
    if (objectType == *IoFileObjectType &&
        (((PFILE_OBJECT)Object)->Busy || ((PFILE_OBJECT)Object)->Waiters))
    {
        status = KphQueryNameFileObject(Object, Buffer, BufferLength, ReturnLength);
        dprintf("KphQueryNameFileObject: status 0x%x\n", status);
    }
    else
    {
        status = ObQueryNameString(Object, Buffer, BufferLength, ReturnLength);
        dprintf("ObQueryNameString: status 0x%x\n", status);
    }

    // Make the error returns consistent.
    if (status == STATUS_BUFFER_OVERFLOW) // returned by I/O subsystem
        status = STATUS_BUFFER_TOO_SMALL;
    if (status == STATUS_INFO_LENGTH_MISMATCH) // returned by ObQueryNameString
        status = STATUS_BUFFER_TOO_SMALL;

    if (NT_SUCCESS(status))
        dprintf("KphQueryNameObject: %.*S\n", Buffer->Name.Length / sizeof(WCHAR), Buffer->Name.Buffer);
    else
        dprintf("KphQueryNameObject: status 0x%x\n", status);

    return status;
}

/**
 * Queries the name of a file object.
 *
 * \param FileObject A pointer to a file object.
 * \param Buffer The buffer in which the object name will be stored.
 * \param BufferLength The number of bytes available in \a Buffer.
 * \param ReturnLength A variable which receives the number of bytes required to be available in
 * \a Buffer.
 */
NTSTATUS KphQueryNameFileObject(
    __in PFILE_OBJECT FileObject,
    __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer,
    __in ULONG BufferLength,
    __out PULONG ReturnLength
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG returnLength;
    PCHAR objectName;
    ULONG usedLength;
    ULONG subNameLength;
    PFILE_OBJECT relatedFileObject;

    PAGED_CODE();

    // We need at least the size of OBJECT_NAME_INFORMATION to continue.
    if (BufferLength < sizeof(OBJECT_NAME_INFORMATION))
    {
        *ReturnLength = sizeof(OBJECT_NAME_INFORMATION);

        return STATUS_BUFFER_TOO_SMALL;
    }

    // Assume failure.
    Buffer->Name.Length = 0;
    // We will place the object name directly after the UNICODE_STRING structure in the buffer.
    Buffer->Name.Buffer = (PWSTR)((ULONG_PTR)Buffer + sizeof(OBJECT_NAME_INFORMATION));
    // Retain a local pointer to the object name so we can manipulate the pointer.
    objectName = (PCHAR)Buffer->Name.Buffer;
    // A variable that keeps track of how much space we have used.
    usedLength = sizeof(OBJECT_NAME_INFORMATION);

    // Check if the file object has an associated device (e.g. "\Device\NamedPipe", "\Device\Mup").
    // We can use the user-supplied buffer for this since if the buffer isn't big enough, we can't
    // proceed anyway (we are going to use the name).
    if (FileObject->DeviceObject)
    {
        status = ObQueryNameString(
            FileObject->DeviceObject,
            Buffer,
            BufferLength,
            &returnLength
            );

        if (!NT_SUCCESS(status))
        {
            if (status == STATUS_INFO_LENGTH_MISMATCH)
                status = STATUS_BUFFER_TOO_SMALL;

            *ReturnLength = returnLength;

            return status;
        }

        // The UNICODE_STRING in the buffer is now filled in. We will append to the object name
        // later, so we need to fix the object name pointer by adding the length, in bytes, of the
        // device name string we just got.
        objectName += Buffer->Name.Length;
        usedLength += Buffer->Name.Length;
    }

    // Check if the file object has a file name component. If not, we can't do anything else, so we
    // just return the name we have already.
    if (!FileObject->FileName.Buffer)
    {
        *ReturnLength = usedLength;

        return STATUS_SUCCESS;
    }

    // The file object has a name. We need to walk up the file object chain and append the names of
    // the related file objects in reverse order. This means we need to calculate the total length
    // first.

    relatedFileObject = FileObject;
    subNameLength = 0;

    do
    {
        subNameLength += relatedFileObject->FileName.Length;

        // Avoid infinite loops.
        if (relatedFileObject == relatedFileObject->RelatedFileObject)
            break;

        relatedFileObject = relatedFileObject->RelatedFileObject;
    }
    while (relatedFileObject);

    usedLength += subNameLength;

    // Check if we have enough space to write the whole thing.
    if (usedLength > BufferLength)
    {
        *ReturnLength = usedLength;

        return STATUS_BUFFER_TOO_SMALL;
    }

    // We're ready to begin copying the names.

    // Add the name length because we're copying in reverse order.
    objectName += subNameLength;

    relatedFileObject = FileObject;

    do
    {
        objectName -= relatedFileObject->FileName.Length;
        memcpy(objectName, relatedFileObject->FileName.Buffer, relatedFileObject->FileName.Length);

        // Avoid infinite loops.
        if (relatedFileObject == relatedFileObject->RelatedFileObject)
            break;

        relatedFileObject = relatedFileObject->RelatedFileObject;
    }
    while (relatedFileObject);

    // Update the length.
    Buffer->Name.Length += (USHORT)subNameLength;

    // Pass the return length back.
    *ReturnLength = usedLength;

    return STATUS_SUCCESS;
}

/**
 * Queries object information.
 *
 * \param ProcessHandle A handle to a process.
 * \param Handle A handle which is present in the process referenced by \a ProcessHandle.
 * \param ObjectInformationClass The type of information to retrieve.
 * \param ObjectInformation The buffer in which the information will be stored.
 * \param ObjectInformationLength The number of bytes available in \a ObjectInformation.
 * \param ReturnLength A variable which receives the number of bytes required to be available in
 * \a ObjectInformation.
 * \param AccessMode The mode in which to perform access checks.
 */
NTSTATUS KpiQueryInformationObject(
    __in HANDLE ProcessHandle,
    __in HANDLE Handle,
    __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass,
    __out_bcount(ObjectInformationLength) PVOID ObjectInformation,
    __in ULONG ObjectInformationLength,
    __out_opt PULONG ReturnLength,
    __in KPROCESSOR_MODE AccessMode
    )
{
    NTSTATUS status;
    PEPROCESS process;
    KPROCESSOR_MODE referenceMode;
    KAPC_STATE apcState;
    ULONG returnLength;

    PAGED_CODE();

    if (AccessMode != KernelMode)
    {
        __try
        {
            ProbeForWrite(ObjectInformation, ObjectInformationLength, sizeof(ULONG));

            if (ReturnLength)
                ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG));
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            return GetExceptionCode();
        }
    }

    status = ObReferenceObjectByHandle(
        ProcessHandle,
        0,
        *PsProcessType,
        AccessMode,
        &process,
        NULL
        );

    if (!NT_SUCCESS(status))
        return status;

    if (process == PsInitialSystemProcess)
    {
        // A check was added in Windows 7 - if we're attached to the System process, the handle must
        // be a kernel handle.
        Handle = MakeKernelHandle(Handle);
        referenceMode = KernelMode;
    }
    else
    {
        // Make sure the handle isn't a kernel handle if we're not attached to the System process.
        // This means we can avoid referencing then opening the objects later when calling
        // ZwQueryObject, etc.
        if (IsKernelHandle(Handle))
        {
            ObDereferenceObject(process);
            return STATUS_INVALID_HANDLE;
        }

        referenceMode = AccessMode;
    }

    switch (ObjectInformationClass)
    {
    case KphObjectBasicInformation:
        {
            OBJECT_BASIC_INFORMATION basicInfo;

            KeStackAttachProcess(process, &apcState);
            status = ZwQueryObject(
                Handle,
                ObjectBasicInformation,
                &basicInfo,
                sizeof(OBJECT_BASIC_INFORMATION),
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                if (ObjectInformationLength == sizeof(OBJECT_BASIC_INFORMATION))
                {
                    __try
                    {
                        *(POBJECT_BASIC_INFORMATION)ObjectInformation = basicInfo;
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }
                else
                {
                    status = STATUS_INFO_LENGTH_MISMATCH;
                }
            }

            returnLength = sizeof(OBJECT_BASIC_INFORMATION);
        }
        break;
    case KphObjectNameInformation:
        {
            PVOID object;
            ULONG allocateSize;
            POBJECT_NAME_INFORMATION nameInfo;

            returnLength = sizeof(OBJECT_NAME_INFORMATION);

            // Attach to the process a get a pointer to the object.
            KeStackAttachProcess(process, &apcState);
            status = ObReferenceObjectByHandle(
                Handle,
                0,
                NULL,
                referenceMode,
                &object,
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                allocateSize = ObjectInformationLength;

                if (allocateSize < sizeof(OBJECT_NAME_INFORMATION)) // make sure we never try to allocate 0 bytes
                    allocateSize = sizeof(OBJECT_NAME_INFORMATION);

                nameInfo = ExAllocatePoolWithQuotaTag(PagedPool, allocateSize, 'QhpK');

                if (nameInfo)
                {
                    // Make sure we don't leak any data.
                    memset(nameInfo, 0, ObjectInformationLength);

                    status = KphQueryNameObject(
                        object,
                        nameInfo,
                        ObjectInformationLength,
                        &returnLength
                        );
                    dprintf("KpiQueryInformationObject: called KphQueryNameObject: Handle: 0x%Ix, ObjectInformationLength: %u, returnLength: %u\n",
                        Handle, ObjectInformationLength, returnLength);

                    if (NT_SUCCESS(status))
                    {
                        // Fix up the buffer pointer.
                        if (nameInfo->Name.Buffer)
                            nameInfo->Name.Buffer = (PVOID)((ULONG_PTR)nameInfo->Name.Buffer - (ULONG_PTR)nameInfo + (ULONG_PTR)ObjectInformation);

                        __try
                        {
                            memcpy(ObjectInformation, nameInfo, ObjectInformationLength);
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER)
                        {
                            status = GetExceptionCode();
                        }
                    }

                    ExFreePoolWithTag(nameInfo, 'QhpK');
                }
                else
                {
                    status = STATUS_INSUFFICIENT_RESOURCES;
                }

                ObDereferenceObject(object);
            }
        }
        break;
    case KphObjectTypeInformation:
        {
            ULONG allocateSize;
            POBJECT_TYPE_INFORMATION typeInfo;

            returnLength = sizeof(OBJECT_TYPE_INFORMATION);
            allocateSize = ObjectInformationLength;

            if (allocateSize < sizeof(OBJECT_TYPE_INFORMATION))
                allocateSize = sizeof(OBJECT_TYPE_INFORMATION);

            // ObQueryTypeInfo uses ObjectType->Name.MaximumLength instead of
            // ObjectType->Name.Length + sizeof(WCHAR) to calculate the required buffer size. In
            // Windows 8, certain object types (e.g. TmTx) do NOT include the null terminator in
            // MaximumLength, which causes ObQueryTypeInfo to overrun the given buffer. To work
            // around this bug, we add some (generous) padding to our allocation.
            allocateSize += sizeof(ULONGLONG);

            typeInfo = ExAllocatePoolWithQuotaTag(PagedPool, allocateSize, 'QhpK');

            if (typeInfo)
            {
                memset(typeInfo, 0, ObjectInformationLength);

                KeStackAttachProcess(process, &apcState);
                status = ZwQueryObject(
                    Handle,
                    ObjectTypeInformation,
                    typeInfo,
                    ObjectInformationLength,
                    &returnLength
                    );
                KeUnstackDetachProcess(&apcState);

                if (NT_SUCCESS(status))
                {
                    // Fix up the buffer pointer.
                    if (typeInfo->TypeName.Buffer)
                        typeInfo->TypeName.Buffer = (PVOID)((ULONG_PTR)typeInfo->TypeName.Buffer - (ULONG_PTR)typeInfo + (ULONG_PTR)ObjectInformation);

                    __try
                    {
                        memcpy(ObjectInformation, typeInfo, ObjectInformationLength);
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }

                ExFreePoolWithTag(typeInfo, 'QhpK');
            }
            else
            {
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
        }
        break;
    case KphObjectHandleFlagInformation:
        {
            OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo;

            KeStackAttachProcess(process, &apcState);
            status = ZwQueryObject(
                Handle,
                ObjectHandleFlagInformation,
                &handleFlagInfo,
                sizeof(OBJECT_HANDLE_FLAG_INFORMATION),
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                if (ObjectInformationLength == sizeof(OBJECT_HANDLE_FLAG_INFORMATION))
                {
                    __try
                    {
                        *(POBJECT_HANDLE_FLAG_INFORMATION)ObjectInformation = handleFlagInfo;
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }
                else
                {
                    status = STATUS_INFO_LENGTH_MISMATCH;
                }
            }

            returnLength = sizeof(OBJECT_HANDLE_FLAG_INFORMATION);
        }
        break;
    case KphObjectProcessBasicInformation:
        {
            PROCESS_BASIC_INFORMATION basicInfo;

            KeStackAttachProcess(process, &apcState);
            status = ZwQueryInformationProcess(
                Handle,
                ProcessBasicInformation,
                &basicInfo,
                sizeof(PROCESS_BASIC_INFORMATION),
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                if (ObjectInformationLength == sizeof(PROCESS_BASIC_INFORMATION))
                {
                    __try
                    {
                        *(PPROCESS_BASIC_INFORMATION)ObjectInformation = basicInfo;
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }
                else
                {
                    status = STATUS_INFO_LENGTH_MISMATCH;
                }
            }

            returnLength = sizeof(PROCESS_BASIC_INFORMATION);
        }
        break;
    case KphObjectThreadBasicInformation:
        {
            THREAD_BASIC_INFORMATION basicInfo;

            KeStackAttachProcess(process, &apcState);
            status = ZwQueryInformationThread(
                Handle,
                ThreadBasicInformation,
                &basicInfo,
                sizeof(THREAD_BASIC_INFORMATION),
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                if (ObjectInformationLength == sizeof(THREAD_BASIC_INFORMATION))
                {
                    __try
                    {
                        *(PTHREAD_BASIC_INFORMATION)ObjectInformation = basicInfo;
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }
                else
                {
                    status = STATUS_INFO_LENGTH_MISMATCH;
                }
            }

            returnLength = sizeof(THREAD_BASIC_INFORMATION);
        }
        break;
    case KphObjectEtwRegBasicInformation:
        {
            PVOID etwReg;
            PVOID objectType;
            PUNICODE_STRING objectTypeName;
            UNICODE_STRING etwRegistrationName;
            PVOID guidEntry;
            ETWREG_BASIC_INFORMATION basicInfo;

            // Check dynamic data requirements.
            if (KphDynEgeGuid != -1 &&
                KphDynEreGuidEntry != -1 &&
                KphDynOtName != -1)
            {
                // Attach to the process and get a pointer to the object. We don't have a pointer to
                // the EtwRegistration object type, so we'll just have to check the type name.

                KeStackAttachProcess(process, &apcState);
                status = ObReferenceObjectByHandle(
                    Handle,
                    0,
                    NULL,
                    referenceMode,
                    &etwReg,
                    NULL
                    );
                KeUnstackDetachProcess(&apcState);

                if (NT_SUCCESS(status))
                {
                    // Check the type name.

                    objectType = ObGetObjectType(etwReg);

                    if (objectType)
                    {
                        objectTypeName = (PUNICODE_STRING)((ULONG_PTR)objectType + KphDynOtName);
                        RtlInitUnicodeString(&etwRegistrationName, L"EtwRegistration");

                        if (!RtlEqualUnicodeString(objectTypeName, &etwRegistrationName, FALSE))
                        {
                            status = STATUS_OBJECT_TYPE_MISMATCH;
                        }
                    }
                    else
                    {
                        status = STATUS_NOT_SUPPORTED;
                    }

                    if (NT_SUCCESS(status))
                    {
                        guidEntry = *(PVOID *)((ULONG_PTR)etwReg + KphDynEreGuidEntry);

                        if (guidEntry)
                            basicInfo.Guid = *(GUID *)((ULONG_PTR)guidEntry + KphDynEgeGuid);
                        else
                            memset(&basicInfo.Guid, 0, sizeof(GUID));

                        basicInfo.SessionId = 0; // not implemented

                        if (ObjectInformationLength == sizeof(ETWREG_BASIC_INFORMATION))
                        {
                            __try
                            {
                                *(PETWREG_BASIC_INFORMATION)ObjectInformation = basicInfo;
                            }
                            __except (EXCEPTION_EXECUTE_HANDLER)
                            {
                                status = GetExceptionCode();
                            }
                        }
                        else
                        {
                            status = STATUS_INFO_LENGTH_MISMATCH;
                        }
                    }

                    ObDereferenceObject(etwReg);
                }
            }
            else
            {
                status = STATUS_NOT_SUPPORTED;
            }

            returnLength = sizeof(ETWREG_BASIC_INFORMATION);
        }
        break;
    case KphObjectFileObjectInformation:
        {
            PFILE_OBJECT fileObject;
            KPH_FILE_OBJECT_INFORMATION objectInfo;

            KeStackAttachProcess(process, &apcState);
            status = ObReferenceObjectByHandle(
                Handle,
                0,
                *IoFileObjectType,
                referenceMode,
                &fileObject,
                NULL
                );
            KeUnstackDetachProcess(&apcState);

            if (NT_SUCCESS(status))
            {
                objectInfo.LockOperation = fileObject->LockOperation;
                objectInfo.DeletePending = fileObject->DeletePending;
                objectInfo.ReadAccess = fileObject->ReadAccess;
                objectInfo.WriteAccess = fileObject->WriteAccess;
                objectInfo.DeleteAccess = fileObject->DeleteAccess;
                objectInfo.SharedRead = fileObject->SharedRead;
                objectInfo.SharedWrite = fileObject->SharedWrite;
                objectInfo.SharedDelete = fileObject->SharedDelete;
                objectInfo.CurrentByteOffset = fileObject->CurrentByteOffset;
                objectInfo.Flags = fileObject->Flags;

                if (ObjectInformationLength == sizeof(KPH_FILE_OBJECT_INFORMATION))
                {
                    __try
                    {
                        *(PKPH_FILE_OBJECT_INFORMATION)ObjectInformation = objectInfo;
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER)
                    {
                        status = GetExceptionCode();
                    }
                }
                else
                {
                    status = STATUS_INFO_LENGTH_MISMATCH;
                }

                ObDereferenceObject(fileObject);
            }

            returnLength = sizeof(KPH_FILE_OBJECT_INFORMATION);
        }
        break;
    case KphObjectFileObjectDriver:
        {
            PFILE_OBJECT fileObject;
            HANDLE driverHandle;

            if (ObjectInformationLength == sizeof(KPH_FILE_OBJECT_DRIVER))
            {
                KeStackAttachProcess(process, &apcState);
                status = ObReferenceObjectByHandle(
                    Handle,
                    0,
                    *IoFileObjectType,
                    referenceMode,
                    &fileObject,
                    NULL
                    );
                KeUnstackDetachProcess(&apcState);

                if (NT_SUCCESS(status))
                {
                    if (fileObject->DeviceObject && fileObject->DeviceObject->DriverObject)
                    {
                        status = ObOpenObjectByPointer(
                            fileObject->DeviceObject->DriverObject,
                            0,
                            NULL,
                            SYNCHRONIZE,
                            *IoDriverObjectType,
                            AccessMode,
                            &driverHandle
                            );
                    }
                    else
                    {
                        driverHandle = NULL;
                    }

                    if (NT_SUCCESS(status))
                    {
                        __try
                        {
                            ((PKPH_FILE_OBJECT_DRIVER)ObjectInformation)->DriverHandle = driverHandle;
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER)
                        {
                            status = GetExceptionCode();
                        }
                    }

                    ObDereferenceObject(fileObject);
                }
            }
            else
            {
                status = STATUS_INFO_LENGTH_MISMATCH;
            }
        }
        break;
    default:
        status = STATUS_INVALID_INFO_CLASS;
        returnLength = 0;
        break;
    }

    ObDereferenceObject(process);

    if (ReturnLength)
    {
        if (AccessMode != KernelMode)
        {
            __try
            {
                *ReturnLength = returnLength;
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                NOTHING;
            }
        }
        else
        {
            *ReturnLength = returnLength;
        }
    }

    return status;
}

/**
 * Sets object information.
 *
 * \param ProcessHandle A handle to a process.
 * \param Handle A handle which is present in the process referenced by \a ProcessHandle.
 * \param ObjectInformationClass The type of information to set.
 * \param ObjectInformation A buffer which contains the information to set.
 * \param ObjectInformationLength The number of bytes present in \a ObjectInformation.
 * \param AccessMode The mode in which to perform access checks.
 */
NTSTATUS KpiSetInformationObject(
    __in HANDLE ProcessHandle,
    __in HANDLE Handle,
    __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass,
    __in_bcount(ObjectInformationLength) PVOID ObjectInformation,
    __in ULONG ObjectInformationLength,
    __in KPROCESSOR_MODE AccessMode
    )
{
    NTSTATUS status;
    PEPROCESS process;
    KAPC_STATE apcState;

    PAGED_CODE();

    if (AccessMode != KernelMode)
    {
        ULONG alignment;

        switch (ObjectInformationClass)
        {
        case KphObjectHandleFlagInformation:
            alignment = sizeof(BOOLEAN);
            break;
        default:
            alignment = sizeof(ULONG);
            break;
        }

        __try
        {
            ProbeForRead(ObjectInformation, ObjectInformationLength, alignment);
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            return GetExceptionCode();
        }
    }

    status = ObReferenceObjectByHandle(
        ProcessHandle,
        0,
        *PsProcessType,
        AccessMode,
        &process,
        NULL
        );

    if (!NT_SUCCESS(status))
        return status;

    if (process == PsInitialSystemProcess)
    {
        Handle = MakeKernelHandle(Handle);
    }
    else
    {
        if (IsKernelHandle(Handle))
        {
            ObDereferenceObject(process);
            return STATUS_INVALID_HANDLE;
        }
    }

    switch (ObjectInformationClass)
    {
    case KphObjectHandleFlagInformation:
        {
            OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo;

            if (ObjectInformationLength == sizeof(OBJECT_HANDLE_FLAG_INFORMATION))
            {
                __try
                {
                    handleFlagInfo = *(POBJECT_HANDLE_FLAG_INFORMATION)ObjectInformation;
                }
                __except (EXCEPTION_EXECUTE_HANDLER)
                {
                    status = GetExceptionCode();
                }
            }
            else
            {
                status = STATUS_INFO_LENGTH_MISMATCH;
            }

            if (NT_SUCCESS(status))
            {
                KeStackAttachProcess(process, &apcState);
                status = ObSetHandleAttributes(Handle, &handleFlagInfo, KernelMode);
                KeUnstackDetachProcess(&apcState);
            }
        }
        break;
    default:
        status = STATUS_INVALID_INFO_CLASS;
        break;
    }

    ObDereferenceObject(process);

    return status;
}

NTSTATUS KphOpenNamedObject(
    __out PHANDLE ObjectHandle,
    __in ACCESS_MASK DesiredAccess,
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __in POBJECT_TYPE ObjectType,
    __in KPROCESSOR_MODE AccessMode
    )
{
    NTSTATUS status;
    HANDLE objectHandle;

    PAGED_CODE();

    // Open the object.
    status = ObOpenObjectByName(
        ObjectAttributes,
        ObjectType,
        AccessMode,
        NULL,
        DesiredAccess,
        NULL,
        &objectHandle
        );

    // Pass the handle back.
    if (AccessMode != KernelMode)
    {
        __try
        {
            *ObjectHandle = objectHandle;
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            status = GetExceptionCode();
        }
    }
    else
    {
        *ObjectHandle = objectHandle;
    }

    return status;
}
