1296 lines
38 KiB
C
1296 lines
38 KiB
C
/*
|
|
* 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;
|
|
}
|