1764 lines
48 KiB
C
1764 lines
48 KiB
C
/*
|
|
* Process Hacker -
|
|
* handle information
|
|
*
|
|
* Copyright (C) 2010-2015 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 <ph.h>
|
|
#include <hndlinfo.h>
|
|
#include <kphuser.h>
|
|
#include <lsasup.h>
|
|
|
|
#define PH_QUERY_HACK_MAX_THREADS 20
|
|
|
|
typedef struct _PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT
|
|
{
|
|
SLIST_ENTRY ListEntry;
|
|
|
|
PUSER_THREAD_START_ROUTINE Routine;
|
|
PVOID Context;
|
|
|
|
HANDLE StartEventHandle;
|
|
HANDLE CompletedEventHandle;
|
|
HANDLE ThreadHandle;
|
|
} PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT, *PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT;
|
|
|
|
typedef enum _PHP_QUERY_OBJECT_WORK
|
|
{
|
|
NtQueryObjectWork,
|
|
NtQuerySecurityObjectWork,
|
|
NtSetSecurityObjectWork
|
|
} PHP_QUERY_OBJECT_WORK;
|
|
|
|
typedef struct _PHP_QUERY_OBJECT_COMMON_CONTEXT
|
|
{
|
|
PHP_QUERY_OBJECT_WORK Work;
|
|
NTSTATUS Status;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
HANDLE Handle;
|
|
OBJECT_INFORMATION_CLASS ObjectInformationClass;
|
|
PVOID ObjectInformation;
|
|
ULONG ObjectInformationLength;
|
|
PULONG ReturnLength;
|
|
} NtQueryObject;
|
|
struct
|
|
{
|
|
HANDLE Handle;
|
|
SECURITY_INFORMATION SecurityInformation;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|
ULONG Length;
|
|
PULONG LengthNeeded;
|
|
} NtQuerySecurityObject;
|
|
struct
|
|
{
|
|
HANDLE Handle;
|
|
SECURITY_INFORMATION SecurityInformation;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|
} NtSetSecurityObject;
|
|
} u;
|
|
} PHP_QUERY_OBJECT_COMMON_CONTEXT, *PPHP_QUERY_OBJECT_COMMON_CONTEXT;
|
|
|
|
PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT PhpAcquireCallWithTimeoutThread(
|
|
_In_opt_ PLARGE_INTEGER Timeout
|
|
);
|
|
|
|
VOID PhpReleaseCallWithTimeoutThread(
|
|
_Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext
|
|
);
|
|
|
|
NTSTATUS PhpCallWithTimeout(
|
|
_Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext,
|
|
_In_ PUSER_THREAD_START_ROUTINE Routine,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PLARGE_INTEGER Timeout
|
|
);
|
|
|
|
NTSTATUS PhpCallWithTimeoutThreadStart(
|
|
_In_ PVOID Parameter
|
|
);
|
|
|
|
static PPH_STRING PhObjectTypeNames[MAX_OBJECT_TYPE_NUMBER] = { 0 };
|
|
static PPH_GET_CLIENT_ID_NAME PhHandleGetClientIdName = PhStdGetClientIdName;
|
|
|
|
static SLIST_HEADER PhpCallWithTimeoutThreadListHead;
|
|
static PH_WAKE_EVENT PhpCallWithTimeoutThreadReleaseEvent = PH_WAKE_EVENT_INIT;
|
|
|
|
PPH_GET_CLIENT_ID_NAME PhSetHandleClientIdFunction(
|
|
_In_ PPH_GET_CLIENT_ID_NAME GetClientIdName
|
|
)
|
|
{
|
|
return _InterlockedExchangePointer(
|
|
(PVOID *)&PhHandleGetClientIdName,
|
|
GetClientIdName
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhpGetObjectBasicInformation(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_Out_ POBJECT_BASIC_INFORMATION BasicInformation
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (KphIsConnected())
|
|
{
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectBasicInformation,
|
|
BasicInformation,
|
|
sizeof(OBJECT_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
// The object was referenced in KProcessHacker, so we need to subtract 1 from the
|
|
// pointer count.
|
|
BasicInformation->PointerCount -= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status = NtQueryObject(
|
|
Handle,
|
|
ObjectBasicInformation,
|
|
BasicInformation,
|
|
sizeof(OBJECT_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
// The object was referenced in NtQueryObject and a handle was opened to the object. We
|
|
// need to subtract 1 from the pointer count, then subtract 1 from both counts.
|
|
BasicInformation->HandleCount -= 1;
|
|
BasicInformation->PointerCount -= 2;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpGetObjectTypeName(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_In_ ULONG ObjectTypeNumber,
|
|
_Out_ PPH_STRING *TypeName
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PPH_STRING typeName = NULL;
|
|
|
|
// If the cache contains the object type name, use it. Otherwise, query the type name.
|
|
|
|
if (ObjectTypeNumber != -1 && ObjectTypeNumber < MAX_OBJECT_TYPE_NUMBER)
|
|
typeName = PhObjectTypeNames[ObjectTypeNumber];
|
|
|
|
if (typeName)
|
|
{
|
|
PhReferenceObject(typeName);
|
|
}
|
|
else
|
|
{
|
|
POBJECT_TYPE_INFORMATION buffer;
|
|
ULONG returnLength = 0;
|
|
PPH_STRING oldTypeName;
|
|
|
|
// Get the needed buffer size.
|
|
if (KphIsConnected())
|
|
{
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectTypeInformation,
|
|
NULL,
|
|
0,
|
|
&returnLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
status = NtQueryObject(
|
|
Handle,
|
|
ObjectTypeInformation,
|
|
NULL,
|
|
0,
|
|
&returnLength
|
|
);
|
|
}
|
|
|
|
if (returnLength == 0)
|
|
return status;
|
|
|
|
buffer = PhAllocate(returnLength);
|
|
|
|
if (KphIsConnected())
|
|
{
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectTypeInformation,
|
|
buffer,
|
|
returnLength,
|
|
&returnLength
|
|
);
|
|
}
|
|
else
|
|
{
|
|
status = NtQueryObject(
|
|
Handle,
|
|
ObjectTypeInformation,
|
|
buffer,
|
|
returnLength,
|
|
&returnLength
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
// Create a copy of the type name.
|
|
typeName = PhCreateStringFromUnicodeString(&buffer->TypeName);
|
|
|
|
if (ObjectTypeNumber != -1 && ObjectTypeNumber < MAX_OBJECT_TYPE_NUMBER)
|
|
{
|
|
// Try to store the type name in the cache.
|
|
oldTypeName = _InterlockedCompareExchangePointer(
|
|
&PhObjectTypeNames[ObjectTypeNumber],
|
|
typeName,
|
|
NULL
|
|
);
|
|
|
|
// Add a reference if we stored the type name
|
|
// successfully.
|
|
if (!oldTypeName)
|
|
PhReferenceObject(typeName);
|
|
}
|
|
|
|
PhFree(buffer);
|
|
}
|
|
|
|
// At this point typeName should contain a type name with one additional reference.
|
|
|
|
*TypeName = typeName;
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpGetObjectName(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_In_ BOOLEAN WithTimeout,
|
|
_Out_ PPH_STRING *ObjectName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
POBJECT_NAME_INFORMATION buffer;
|
|
ULONG bufferSize;
|
|
ULONG attempts = 8;
|
|
|
|
bufferSize = 0x200;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
// A loop is needed because the I/O subsystem likes to give us the wrong return lengths...
|
|
do
|
|
{
|
|
if (KphIsConnected())
|
|
{
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectNameInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (WithTimeout)
|
|
{
|
|
status = PhCallNtQueryObjectWithTimeout(
|
|
Handle,
|
|
ObjectNameInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
else
|
|
{
|
|
status = NtQueryObject(
|
|
Handle,
|
|
ObjectNameInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
}
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH ||
|
|
status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
} while (--attempts);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*ObjectName = PhCreateStringFromUnicodeString(&buffer->Name);
|
|
}
|
|
|
|
PhFree(buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
PPH_STRING PhFormatNativeKeyName(
|
|
_In_ PPH_STRING Name
|
|
)
|
|
{
|
|
static PH_STRINGREF hklmPrefix = PH_STRINGREF_INIT(L"\\Registry\\Machine");
|
|
static PH_STRINGREF hkcrPrefix = PH_STRINGREF_INIT(L"\\Registry\\Machine\\Software\\Classes");
|
|
static PH_STRINGREF hkuPrefix = PH_STRINGREF_INIT(L"\\Registry\\User");
|
|
static PPH_STRING hkcuPrefix;
|
|
static PPH_STRING hkcucrPrefix;
|
|
|
|
static PH_STRINGREF hklmString = PH_STRINGREF_INIT(L"HKLM");
|
|
static PH_STRINGREF hkcrString = PH_STRINGREF_INIT(L"HKCR");
|
|
static PH_STRINGREF hkuString = PH_STRINGREF_INIT(L"HKU");
|
|
static PH_STRINGREF hkcuString = PH_STRINGREF_INIT(L"HKCU");
|
|
static PH_STRINGREF hkcucrString = PH_STRINGREF_INIT(L"HKCU\\Software\\Classes");
|
|
|
|
static PH_INITONCE initOnce = PH_INITONCE_INIT;
|
|
|
|
PPH_STRING newName;
|
|
PH_STRINGREF name;
|
|
|
|
if (PhBeginInitOnce(&initOnce))
|
|
{
|
|
HANDLE currentTokenHandle;
|
|
PTOKEN_USER tokenUser;
|
|
PPH_STRING stringSid = NULL;
|
|
|
|
currentTokenHandle = PhGetOwnTokenAttributes().TokenHandle;
|
|
|
|
if (currentTokenHandle && NT_SUCCESS(PhGetTokenUser(
|
|
currentTokenHandle,
|
|
&tokenUser
|
|
)))
|
|
{
|
|
stringSid = PhSidToStringSid(tokenUser->User.Sid);
|
|
PhFree(tokenUser);
|
|
}
|
|
|
|
if (stringSid)
|
|
{
|
|
static PH_STRINGREF registryUserPrefix = PH_STRINGREF_INIT(L"\\Registry\\User\\");
|
|
static PH_STRINGREF classesString = PH_STRINGREF_INIT(L"_Classes");
|
|
|
|
hkcuPrefix = PhConcatStringRef2(®istryUserPrefix, &stringSid->sr);
|
|
hkcucrPrefix = PhConcatStringRef2(&hkcuPrefix->sr, &classesString);
|
|
|
|
PhDereferenceObject(stringSid);
|
|
}
|
|
else
|
|
{
|
|
hkcuPrefix = PhCreateString(L"..."); // some random string that won't ever get matched
|
|
hkcucrPrefix = PhCreateString(L"...");
|
|
}
|
|
|
|
PhEndInitOnce(&initOnce);
|
|
}
|
|
|
|
name = Name->sr;
|
|
|
|
if (PhStartsWithStringRef(&name, &hkcrPrefix, TRUE))
|
|
{
|
|
PhSkipStringRef(&name, hkcrPrefix.Length);
|
|
newName = PhConcatStringRef2(&hkcrString, &name);
|
|
}
|
|
else if (PhStartsWithStringRef(&name, &hklmPrefix, TRUE))
|
|
{
|
|
PhSkipStringRef(&name, hklmPrefix.Length);
|
|
newName = PhConcatStringRef2(&hklmString, &name);
|
|
}
|
|
else if (PhStartsWithStringRef(&name, &hkcucrPrefix->sr, TRUE))
|
|
{
|
|
PhSkipStringRef(&name, hkcucrPrefix->Length);
|
|
newName = PhConcatStringRef2(&hkcucrString, &name);
|
|
}
|
|
else if (PhStartsWithStringRef(&name, &hkcuPrefix->sr, TRUE))
|
|
{
|
|
PhSkipStringRef(&name, hkcuPrefix->Length);
|
|
newName = PhConcatStringRef2(&hkcuString, &name);
|
|
}
|
|
else if (PhStartsWithStringRef(&name, &hkuPrefix, TRUE))
|
|
{
|
|
PhSkipStringRef(&name, hkuPrefix.Length);
|
|
newName = PhConcatStringRef2(&hkuString, &name);
|
|
}
|
|
else
|
|
{
|
|
PhSetReference(&newName, Name);
|
|
}
|
|
|
|
return newName;
|
|
}
|
|
|
|
NTSTATUS PhGetSectionFileName(
|
|
_In_ HANDLE SectionHandle,
|
|
_Out_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SIZE_T viewSize;
|
|
PVOID viewBase;
|
|
|
|
viewSize = 1;
|
|
viewBase = NULL;
|
|
|
|
status = NtMapViewOfSection(
|
|
SectionHandle,
|
|
NtCurrentProcess(),
|
|
&viewBase,
|
|
0,
|
|
0,
|
|
NULL,
|
|
&viewSize,
|
|
ViewShare,
|
|
0,
|
|
PAGE_READONLY
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
status = PhGetProcessMappedFileName(NtCurrentProcess(), viewBase, FileName);
|
|
NtUnmapViewOfSection(NtCurrentProcess(), viewBase);
|
|
|
|
return status;
|
|
}
|
|
|
|
_Callback_ PPH_STRING PhStdGetClientIdName(
|
|
_In_ PCLIENT_ID ClientId
|
|
)
|
|
{
|
|
static PH_QUEUED_LOCK cachedProcessesLock = PH_QUEUED_LOCK_INIT;
|
|
static PVOID processes = NULL;
|
|
static ULONG lastProcessesTickCount = 0;
|
|
|
|
PPH_STRING name;
|
|
ULONG tickCount;
|
|
PSYSTEM_PROCESS_INFORMATION processInfo;
|
|
|
|
// Get a new process list only if 2 seconds have passed since the last update.
|
|
|
|
tickCount = GetTickCount();
|
|
|
|
if (tickCount - lastProcessesTickCount >= 2000)
|
|
{
|
|
PhAcquireQueuedLockExclusive(&cachedProcessesLock);
|
|
|
|
// Re-check the tick count.
|
|
if (tickCount - lastProcessesTickCount >= 2000)
|
|
{
|
|
if (processes)
|
|
{
|
|
PhFree(processes);
|
|
processes = NULL;
|
|
}
|
|
|
|
if (!NT_SUCCESS(PhEnumProcesses(&processes)))
|
|
{
|
|
PhReleaseQueuedLockExclusive(&cachedProcessesLock);
|
|
return PhCreateString(L"(Error querying processes)");
|
|
}
|
|
|
|
lastProcessesTickCount = tickCount;
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&cachedProcessesLock);
|
|
}
|
|
|
|
// Get a lock on the process list and get a name for the client ID.
|
|
|
|
PhAcquireQueuedLockShared(&cachedProcessesLock);
|
|
|
|
if (!processes)
|
|
{
|
|
PhReleaseQueuedLockShared(&cachedProcessesLock);
|
|
return NULL;
|
|
}
|
|
|
|
processInfo = PhFindProcessInformation(processes, ClientId->UniqueProcess);
|
|
|
|
if (ClientId->UniqueThread)
|
|
{
|
|
if (processInfo)
|
|
{
|
|
name = PhFormatString(
|
|
L"%.*s (%u): %u",
|
|
processInfo->ImageName.Length / 2,
|
|
processInfo->ImageName.Buffer,
|
|
HandleToUlong(ClientId->UniqueProcess),
|
|
HandleToUlong(ClientId->UniqueThread)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
name = PhFormatString(
|
|
L"Non-existent process (%u): %u",
|
|
HandleToUlong(ClientId->UniqueProcess),
|
|
HandleToUlong(ClientId->UniqueThread)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (processInfo)
|
|
{
|
|
name = PhFormatString(
|
|
L"%.*s (%u)",
|
|
processInfo->ImageName.Length / 2,
|
|
processInfo->ImageName.Buffer,
|
|
HandleToUlong(ClientId->UniqueProcess)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
name = PhFormatString(L"Non-existent process (%u)", HandleToUlong(ClientId->UniqueProcess));
|
|
}
|
|
}
|
|
|
|
PhReleaseQueuedLockShared(&cachedProcessesLock);
|
|
|
|
return name;
|
|
}
|
|
|
|
NTSTATUS PhpGetBestObjectName(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_In_ PPH_STRING ObjectName,
|
|
_In_ PPH_STRING TypeName,
|
|
_Out_ PPH_STRING *BestObjectName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPH_STRING bestObjectName = NULL;
|
|
PPH_GET_CLIENT_ID_NAME handleGetClientIdName = PhHandleGetClientIdName;
|
|
|
|
if (PhEqualString2(TypeName, L"EtwRegistration", TRUE))
|
|
{
|
|
if (KphIsConnected())
|
|
{
|
|
ETWREG_BASIC_INFORMATION basicInfo;
|
|
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectEtwRegBasicInformation,
|
|
&basicInfo,
|
|
sizeof(ETWREG_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
static PH_STRINGREF publishersKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\");
|
|
|
|
PPH_STRING guidString;
|
|
PPH_STRING keyName;
|
|
HANDLE keyHandle;
|
|
PPH_STRING publisherName = NULL;
|
|
|
|
guidString = PhFormatGuid(&basicInfo.Guid);
|
|
|
|
// We should perform a lookup on the GUID to get the publisher name.
|
|
|
|
keyName = PhConcatStringRef2(&publishersKeyName, &guidString->sr);
|
|
|
|
if (NT_SUCCESS(PhOpenKey(
|
|
&keyHandle,
|
|
KEY_READ,
|
|
PH_KEY_LOCAL_MACHINE,
|
|
&keyName->sr,
|
|
0
|
|
)))
|
|
{
|
|
publisherName = PhQueryRegistryString(keyHandle, NULL);
|
|
|
|
if (publisherName && publisherName->Length == 0)
|
|
{
|
|
PhDereferenceObject(publisherName);
|
|
publisherName = NULL;
|
|
}
|
|
|
|
NtClose(keyHandle);
|
|
}
|
|
|
|
PhDereferenceObject(keyName);
|
|
|
|
if (publisherName)
|
|
{
|
|
bestObjectName = publisherName;
|
|
PhDereferenceObject(guidString);
|
|
}
|
|
else
|
|
{
|
|
bestObjectName = guidString;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (PhEqualString2(TypeName, L"File", TRUE))
|
|
{
|
|
// Convert the file name to a DOS file name.
|
|
bestObjectName = PhResolveDevicePrefix(ObjectName);
|
|
|
|
if (!bestObjectName)
|
|
{
|
|
// The file doesn't have a DOS name.
|
|
PhSetReference(&bestObjectName, ObjectName);
|
|
}
|
|
|
|
if (PhIsNullOrEmptyString(bestObjectName) && KphIsConnected())
|
|
{
|
|
KPH_FILE_OBJECT_DRIVER fileObjectDriver;
|
|
PPH_STRING driverName;
|
|
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectFileObjectDriver,
|
|
&fileObjectDriver,
|
|
sizeof(KPH_FILE_OBJECT_DRIVER),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(status) && fileObjectDriver.DriverHandle)
|
|
{
|
|
if (NT_SUCCESS(PhGetDriverName(fileObjectDriver.DriverHandle, &driverName)))
|
|
{
|
|
static PH_STRINGREF prefix = PH_STRINGREF_INIT(L"Unnamed file: ");
|
|
|
|
PhMoveReference(&bestObjectName, PhConcatStringRef2(&prefix, &driverName->sr));
|
|
PhDereferenceObject(driverName);
|
|
}
|
|
|
|
NtClose(fileObjectDriver.DriverHandle);
|
|
}
|
|
}
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Job", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
PJOBOBJECT_BASIC_PROCESS_ID_LIST processIdList;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
JOB_OBJECT_QUERY,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
if (handleGetClientIdName && NT_SUCCESS(PhGetJobProcessIdList(dupHandle, &processIdList)))
|
|
{
|
|
PH_STRING_BUILDER sb;
|
|
ULONG i;
|
|
CLIENT_ID clientId;
|
|
PPH_STRING name;
|
|
|
|
PhInitializeStringBuilder(&sb, 40);
|
|
clientId.UniqueThread = NULL;
|
|
|
|
for (i = 0; i < processIdList->NumberOfProcessIdsInList; i++)
|
|
{
|
|
clientId.UniqueProcess = (HANDLE)processIdList->ProcessIdList[i];
|
|
name = handleGetClientIdName(&clientId);
|
|
|
|
if (name)
|
|
{
|
|
PhAppendStringBuilder(&sb, &name->sr);
|
|
PhAppendStringBuilder2(&sb, L"; ");
|
|
PhDereferenceObject(name);
|
|
}
|
|
}
|
|
|
|
PhFree(processIdList);
|
|
|
|
if (sb.String->Length != 0)
|
|
PhRemoveEndStringBuilder(&sb, 2);
|
|
|
|
if (sb.String->Length == 0)
|
|
PhAppendStringBuilder2(&sb, L"(No processes)");
|
|
|
|
bestObjectName = PhFinalStringBuilderString(&sb);
|
|
}
|
|
|
|
NtClose(dupHandle);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Key", TRUE))
|
|
{
|
|
bestObjectName = PhFormatNativeKeyName(ObjectName);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Process", TRUE))
|
|
{
|
|
CLIENT_ID clientId;
|
|
|
|
clientId.UniqueThread = NULL;
|
|
|
|
if (KphIsConnected())
|
|
{
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectProcessBasicInformation,
|
|
&basicInfo,
|
|
sizeof(PROCESS_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
clientId.UniqueProcess = basicInfo.UniqueProcessId;
|
|
}
|
|
else
|
|
{
|
|
HANDLE dupHandle;
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
ProcessQueryAccess,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetProcessBasicInformation(dupHandle, &basicInfo);
|
|
NtClose(dupHandle);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
clientId.UniqueProcess = basicInfo.UniqueProcessId;
|
|
}
|
|
|
|
if (handleGetClientIdName)
|
|
bestObjectName = handleGetClientIdName(&clientId);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Section", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
PPH_STRING fileName;
|
|
|
|
if (!PhIsNullOrEmptyString(ObjectName))
|
|
goto CleanupExit;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
SECTION_QUERY | SECTION_MAP_READ,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetSectionFileName(dupHandle, &fileName);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
bestObjectName = PhResolveDevicePrefix(fileName);
|
|
PhDereferenceObject(fileName);
|
|
}
|
|
else
|
|
{
|
|
SECTION_BASIC_INFORMATION basicInfo;
|
|
|
|
if (NT_SUCCESS(PhGetSectionBasicInformation(dupHandle, &basicInfo)))
|
|
{
|
|
PH_FORMAT format[4];
|
|
PWSTR sectionType = L"Unknown";
|
|
|
|
if (basicInfo.AllocationAttributes & SEC_COMMIT)
|
|
sectionType = L"Commit";
|
|
else if (basicInfo.AllocationAttributes & SEC_FILE)
|
|
sectionType = L"File";
|
|
else if (basicInfo.AllocationAttributes & SEC_IMAGE)
|
|
sectionType = L"Image";
|
|
else if (basicInfo.AllocationAttributes & SEC_RESERVE)
|
|
sectionType = L"Reserve";
|
|
|
|
PhInitFormatS(&format[0], sectionType);
|
|
PhInitFormatS(&format[1], L" (");
|
|
PhInitFormatSize(&format[2], basicInfo.MaximumSize.QuadPart);
|
|
PhInitFormatC(&format[3], ')');
|
|
bestObjectName = PhFormat(format, 4, 20);
|
|
}
|
|
}
|
|
|
|
NtClose(dupHandle);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Thread", TRUE))
|
|
{
|
|
CLIENT_ID clientId;
|
|
|
|
if (KphIsConnected())
|
|
{
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
|
|
status = KphQueryInformationObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
KphObjectThreadBasicInformation,
|
|
&basicInfo,
|
|
sizeof(THREAD_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
clientId = basicInfo.ClientId;
|
|
}
|
|
else
|
|
{
|
|
HANDLE dupHandle;
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
ThreadQueryAccess,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetThreadBasicInformation(dupHandle, &basicInfo);
|
|
NtClose(dupHandle);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
clientId = basicInfo.ClientId;
|
|
}
|
|
|
|
if (handleGetClientIdName)
|
|
bestObjectName = handleGetClientIdName(&clientId);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"TmEn", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
ENLISTMENT_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
ENLISTMENT_QUERY_INFORMATION,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetEnlistmentBasicInformation(dupHandle, &basicInfo);
|
|
NtClose(dupHandle);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
bestObjectName = PhFormatGuid(&basicInfo.EnlistmentId);
|
|
}
|
|
}
|
|
else if (PhEqualString2(TypeName, L"TmRm", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
GUID guid;
|
|
PPH_STRING description;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
RESOURCEMANAGER_QUERY_INFORMATION,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetResourceManagerBasicInformation(
|
|
dupHandle,
|
|
&guid,
|
|
&description
|
|
);
|
|
NtClose(dupHandle);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
if (!PhIsNullOrEmptyString(description))
|
|
{
|
|
bestObjectName = description;
|
|
}
|
|
else
|
|
{
|
|
bestObjectName = PhFormatGuid(&guid);
|
|
|
|
if (description)
|
|
PhDereferenceObject(description);
|
|
}
|
|
}
|
|
}
|
|
else if (PhEqualString2(TypeName, L"TmTm", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
PPH_STRING logFileName = NULL;
|
|
TRANSACTIONMANAGER_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
TRANSACTIONMANAGER_QUERY_INFORMATION,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetTransactionManagerLogFileName(
|
|
dupHandle,
|
|
&logFileName
|
|
);
|
|
|
|
if (NT_SUCCESS(status) && !PhIsNullOrEmptyString(logFileName))
|
|
{
|
|
bestObjectName = PhGetFileName(logFileName);
|
|
PhDereferenceObject(logFileName);
|
|
}
|
|
else
|
|
{
|
|
if (logFileName)
|
|
PhDereferenceObject(logFileName);
|
|
|
|
status = PhGetTransactionManagerBasicInformation(
|
|
dupHandle,
|
|
&basicInfo
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
bestObjectName = PhFormatGuid(&basicInfo.TmIdentity);
|
|
}
|
|
}
|
|
|
|
NtClose(dupHandle);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"TmTx", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
PPH_STRING description = NULL;
|
|
TRANSACTION_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
TRANSACTION_QUERY_INFORMATION,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetTransactionPropertiesInformation(
|
|
dupHandle,
|
|
NULL,
|
|
NULL,
|
|
&description
|
|
);
|
|
|
|
if (NT_SUCCESS(status) && !PhIsNullOrEmptyString(description))
|
|
{
|
|
bestObjectName = description;
|
|
}
|
|
else
|
|
{
|
|
if (description)
|
|
PhDereferenceObject(description);
|
|
|
|
status = PhGetTransactionBasicInformation(
|
|
dupHandle,
|
|
&basicInfo
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
bestObjectName = PhFormatGuid(&basicInfo.TransactionId);
|
|
}
|
|
}
|
|
|
|
NtClose(dupHandle);
|
|
}
|
|
else if (PhEqualString2(TypeName, L"Token", TRUE))
|
|
{
|
|
HANDLE dupHandle;
|
|
PTOKEN_USER tokenUser = NULL;
|
|
TOKEN_STATISTICS statistics = { 0 };
|
|
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
TOKEN_QUERY,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetTokenUser(dupHandle, &tokenUser);
|
|
PhGetTokenStatistics(dupHandle, &statistics);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
PPH_STRING fullName;
|
|
|
|
fullName = PhGetSidFullName(tokenUser->User.Sid, TRUE, NULL);
|
|
|
|
if (fullName)
|
|
{
|
|
PH_FORMAT format[4];
|
|
|
|
PhInitFormatSR(&format[0], fullName->sr);
|
|
PhInitFormatS(&format[1], L": 0x");
|
|
PhInitFormatX(&format[2], statistics.AuthenticationId.LowPart);
|
|
PhInitFormatS(&format[3], statistics.TokenType == TokenPrimary ? L" (Primary)" : L" (Impersonation)");
|
|
|
|
bestObjectName = PhFormat(format, 4, fullName->Length + 8 + 16 + 16);
|
|
PhDereferenceObject(fullName);
|
|
}
|
|
|
|
PhFree(tokenUser);
|
|
}
|
|
|
|
NtClose(dupHandle);
|
|
}
|
|
|
|
CleanupExit:
|
|
|
|
if (!bestObjectName)
|
|
PhSetReference(&bestObjectName, ObjectName);
|
|
|
|
*BestObjectName = bestObjectName;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Gets information for a handle.
|
|
*
|
|
* \param ProcessHandle A handle to the process in which the handle resides.
|
|
* \param Handle The handle value.
|
|
* \param ObjectTypeNumber The object type number of the handle. You can specify -1 for this
|
|
* parameter if the object type number is not known.
|
|
* \param BasicInformation A variable which receives basic information about the object.
|
|
* \param TypeName A variable which receives the object type name.
|
|
* \param ObjectName A variable which receives the object name.
|
|
* \param BestObjectName A variable which receives the formatted object name.
|
|
*
|
|
* \retval STATUS_INVALID_HANDLE The handle specified in \c ProcessHandle or \c Handle is invalid.
|
|
* \retval STATUS_INVALID_PARAMETER_3 The value specified in \c ObjectTypeNumber is invalid.
|
|
*/
|
|
NTSTATUS PhGetHandleInformation(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_In_ ULONG ObjectTypeNumber,
|
|
_Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation,
|
|
_Out_opt_ PPH_STRING *TypeName,
|
|
_Out_opt_ PPH_STRING *ObjectName,
|
|
_Out_opt_ PPH_STRING *BestObjectName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
NTSTATUS subStatus;
|
|
|
|
status = PhGetHandleInformationEx(
|
|
ProcessHandle,
|
|
Handle,
|
|
ObjectTypeNumber,
|
|
0,
|
|
&subStatus,
|
|
BasicInformation,
|
|
TypeName,
|
|
ObjectName,
|
|
BestObjectName,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Fail if any component failed, for compatibility reasons.
|
|
if (!NT_SUCCESS(subStatus))
|
|
{
|
|
if (TypeName)
|
|
PhClearReference(TypeName);
|
|
if (ObjectName)
|
|
PhClearReference(ObjectName);
|
|
if (BestObjectName)
|
|
PhClearReference(BestObjectName);
|
|
|
|
return subStatus;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets information for a handle.
|
|
*
|
|
* \param ProcessHandle A handle to the process in which the handle resides.
|
|
* \param Handle The handle value.
|
|
* \param ObjectTypeNumber The object type number of the handle. You can specify -1 for this
|
|
* parameter if the object type number is not known.
|
|
* \param Flags Reserved.
|
|
* \param SubStatus A variable which receives the NTSTATUS value of the last component that fails.
|
|
* If all operations succeed, the value will be STATUS_SUCCESS. If the function returns an error
|
|
* status, this variable is not set.
|
|
* \param BasicInformation A variable which receives basic information about the object.
|
|
* \param TypeName A variable which receives the object type name.
|
|
* \param ObjectName A variable which receives the object name.
|
|
* \param BestObjectName A variable which receives the formatted object name.
|
|
* \param ExtraInformation Reserved.
|
|
*
|
|
* \retval STATUS_INVALID_HANDLE The handle specified in \c ProcessHandle or \c Handle is invalid.
|
|
* \retval STATUS_INVALID_PARAMETER_3 The value specified in \c ObjectTypeNumber is invalid.
|
|
*
|
|
* \remarks If \a BasicInformation or \a TypeName are specified, the function will fail if either
|
|
* cannot be queried. \a ObjectName, \a BestObjectName and \a ExtraInformation will be NULL if they
|
|
* cannot be queried.
|
|
*/
|
|
NTSTATUS PhGetHandleInformationEx(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ HANDLE Handle,
|
|
_In_ ULONG ObjectTypeNumber,
|
|
_Reserved_ ULONG Flags,
|
|
_Out_opt_ PNTSTATUS SubStatus,
|
|
_Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation,
|
|
_Out_opt_ PPH_STRING *TypeName,
|
|
_Out_opt_ PPH_STRING *ObjectName,
|
|
_Out_opt_ PPH_STRING *BestObjectName,
|
|
_Reserved_ PVOID *ExtraInformation
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
NTSTATUS subStatus = STATUS_SUCCESS;
|
|
HANDLE dupHandle = NULL;
|
|
PPH_STRING typeName = NULL;
|
|
PPH_STRING objectName = NULL;
|
|
PPH_STRING bestObjectName = NULL;
|
|
|
|
if (Handle == NULL || Handle == NtCurrentProcess() || Handle == NtCurrentThread())
|
|
return STATUS_INVALID_HANDLE;
|
|
if (ObjectTypeNumber != -1 && ObjectTypeNumber >= MAX_OBJECT_TYPE_NUMBER)
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
|
|
// Duplicate the handle if we're not using KPH.
|
|
if (!KphIsConnected())
|
|
{
|
|
// However, we obviously don't need to duplicate it
|
|
// if the handle is in the current process.
|
|
if (ProcessHandle != NtCurrentProcess())
|
|
{
|
|
status = NtDuplicateObject(
|
|
ProcessHandle,
|
|
Handle,
|
|
NtCurrentProcess(),
|
|
&dupHandle,
|
|
0,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
dupHandle = Handle;
|
|
}
|
|
}
|
|
|
|
// Get basic information.
|
|
if (BasicInformation)
|
|
{
|
|
status = PhpGetObjectBasicInformation(
|
|
ProcessHandle,
|
|
KphIsConnected() ? Handle : dupHandle,
|
|
BasicInformation
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
}
|
|
|
|
// Exit early if we don't need to get any other information.
|
|
if (!TypeName && !ObjectName && !BestObjectName)
|
|
goto CleanupExit;
|
|
|
|
// Get the type name.
|
|
status = PhpGetObjectTypeName(
|
|
ProcessHandle,
|
|
KphIsConnected() ? Handle : dupHandle,
|
|
ObjectTypeNumber,
|
|
&typeName
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
// Exit early if we don't need to get the object name.
|
|
if (!ObjectName && !BestObjectName)
|
|
goto CleanupExit;
|
|
|
|
// Get the object name.
|
|
// If we're dealing with a file handle we must take special precautions so we don't hang.
|
|
if (PhEqualString2(typeName, L"File", TRUE) && !KphIsConnected())
|
|
{
|
|
#define QUERY_NORMALLY 0
|
|
#define QUERY_WITH_TIMEOUT 1
|
|
#define QUERY_FAIL 2
|
|
|
|
ULONG hackLevel = QUERY_WITH_TIMEOUT;
|
|
|
|
// We can't use the timeout method on XP because hanging threads can't even be terminated!
|
|
if (WindowsVersion <= WINDOWS_XP)
|
|
hackLevel = QUERY_FAIL;
|
|
|
|
if (hackLevel == QUERY_NORMALLY || hackLevel == QUERY_WITH_TIMEOUT)
|
|
{
|
|
status = PhpGetObjectName(
|
|
ProcessHandle,
|
|
KphIsConnected() ? Handle : dupHandle,
|
|
hackLevel == QUERY_WITH_TIMEOUT,
|
|
&objectName
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Pretend the file object has no name.
|
|
objectName = PhReferenceEmptyString();
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Query the object normally.
|
|
status = PhpGetObjectName(
|
|
ProcessHandle,
|
|
KphIsConnected() ? Handle : dupHandle,
|
|
FALSE,
|
|
&objectName
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
if (PhEqualString2(typeName, L"File", TRUE) && KphIsConnected())
|
|
{
|
|
// PhpGetBestObjectName can provide us with a name.
|
|
objectName = PhReferenceEmptyString();
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
subStatus = status;
|
|
status = STATUS_SUCCESS;
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
|
|
// Exit early if we don't need to get the best object name.
|
|
if (!BestObjectName)
|
|
goto CleanupExit;
|
|
|
|
status = PhpGetBestObjectName(
|
|
ProcessHandle,
|
|
Handle,
|
|
objectName,
|
|
typeName,
|
|
&bestObjectName
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
subStatus = status;
|
|
status = STATUS_SUCCESS;
|
|
goto CleanupExit;
|
|
}
|
|
|
|
CleanupExit:
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
if (SubStatus)
|
|
*SubStatus = subStatus;
|
|
if (TypeName)
|
|
PhSetReference(TypeName, typeName);
|
|
if (ObjectName)
|
|
PhSetReference(ObjectName, objectName);
|
|
if (BestObjectName)
|
|
PhSetReference(BestObjectName, bestObjectName);
|
|
}
|
|
|
|
if (dupHandle && ProcessHandle != NtCurrentProcess())
|
|
NtClose(dupHandle);
|
|
|
|
PhClearReference(&typeName);
|
|
PhClearReference(&objectName);
|
|
PhClearReference(&bestObjectName);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhEnumObjectTypes(
|
|
_Out_ POBJECT_TYPES_INFORMATION *ObjectTypes
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
bufferSize = 0x1000;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while ((status = NtQueryObject(
|
|
NULL,
|
|
ObjectTypesInformation,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
)) == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
// Fail if we're resizing the buffer to something very large.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*ObjectTypes = (POBJECT_TYPES_INFORMATION)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
ULONG PhGetObjectTypeNumber(
|
|
_In_ PUNICODE_STRING TypeName
|
|
)
|
|
{
|
|
POBJECT_TYPES_INFORMATION objectTypes;
|
|
POBJECT_TYPE_INFORMATION objectType;
|
|
ULONG i;
|
|
|
|
if (NT_SUCCESS(PhEnumObjectTypes(&objectTypes)))
|
|
{
|
|
objectType = PH_FIRST_OBJECT_TYPE(objectTypes);
|
|
|
|
for (i = 0; i < objectTypes->NumberOfTypes; i++)
|
|
{
|
|
if (RtlEqualUnicodeString(&objectType->TypeName, TypeName, TRUE))
|
|
{
|
|
if (WindowsVersion >= WINDOWS_8_1)
|
|
return objectType->TypeIndex;
|
|
else if (WindowsVersion >= WINDOWS_7)
|
|
return i + 2;
|
|
else
|
|
return i + 1;
|
|
}
|
|
|
|
objectType = PH_NEXT_OBJECT_TYPE(objectType);
|
|
}
|
|
|
|
PhFree(objectTypes);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT PhpAcquireCallWithTimeoutThread(
|
|
_In_opt_ PLARGE_INTEGER Timeout
|
|
)
|
|
{
|
|
static PH_INITONCE initOnce = PH_INITONCE_INIT;
|
|
|
|
PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext;
|
|
PSLIST_ENTRY listEntry;
|
|
PH_QUEUED_WAIT_BLOCK waitBlock;
|
|
|
|
if (PhBeginInitOnce(&initOnce))
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < PH_QUERY_HACK_MAX_THREADS; i++)
|
|
{
|
|
threadContext = PhAllocate(sizeof(PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT));
|
|
memset(threadContext, 0, sizeof(PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT));
|
|
RtlInterlockedPushEntrySList(&PhpCallWithTimeoutThreadListHead, &threadContext->ListEntry);
|
|
}
|
|
|
|
PhEndInitOnce(&initOnce);
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
if (listEntry = RtlInterlockedPopEntrySList(&PhpCallWithTimeoutThreadListHead))
|
|
break;
|
|
|
|
if (!Timeout || Timeout->QuadPart != 0)
|
|
{
|
|
PhQueueWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock);
|
|
|
|
if (listEntry = RtlInterlockedPopEntrySList(&PhpCallWithTimeoutThreadListHead))
|
|
{
|
|
// A new entry has just become available; cancel the wait.
|
|
PhSetWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhWaitForWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock, FALSE, Timeout);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return CONTAINING_RECORD(listEntry, PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT, ListEntry);
|
|
}
|
|
|
|
VOID PhpReleaseCallWithTimeoutThread(
|
|
_Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext
|
|
)
|
|
{
|
|
RtlInterlockedPushEntrySList(&PhpCallWithTimeoutThreadListHead, &ThreadContext->ListEntry);
|
|
PhSetWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, NULL);
|
|
}
|
|
|
|
NTSTATUS PhpCallWithTimeout(
|
|
_Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext,
|
|
_In_ PUSER_THREAD_START_ROUTINE Routine,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PLARGE_INTEGER Timeout
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
// Create objects if necessary.
|
|
|
|
if (!ThreadContext->StartEventHandle)
|
|
{
|
|
if (!NT_SUCCESS(status = NtCreateEvent(&ThreadContext->StartEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))
|
|
return status;
|
|
}
|
|
|
|
if (!ThreadContext->CompletedEventHandle)
|
|
{
|
|
if (!NT_SUCCESS(status = NtCreateEvent(&ThreadContext->CompletedEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))
|
|
return status;
|
|
}
|
|
|
|
// Create a query thread if we don't have one.
|
|
if (!ThreadContext->ThreadHandle)
|
|
{
|
|
CLIENT_ID clientId;
|
|
|
|
NtClearEvent(ThreadContext->StartEventHandle);
|
|
NtClearEvent(ThreadContext->CompletedEventHandle);
|
|
|
|
if (!NT_SUCCESS(status = RtlCreateUserThread(
|
|
NtCurrentProcess(),
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
0,
|
|
32 * 1024,
|
|
PhpCallWithTimeoutThreadStart,
|
|
ThreadContext,
|
|
&ThreadContext->ThreadHandle,
|
|
&clientId)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
// Wait for the thread to initialize.
|
|
NtWaitForSingleObject(ThreadContext->CompletedEventHandle, FALSE, NULL);
|
|
}
|
|
|
|
ThreadContext->Routine = Routine;
|
|
ThreadContext->Context = Context;
|
|
|
|
NtSetEvent(ThreadContext->StartEventHandle, NULL);
|
|
status = NtWaitForSingleObject(ThreadContext->CompletedEventHandle, FALSE, Timeout);
|
|
|
|
ThreadContext->Routine = NULL;
|
|
MemoryBarrier();
|
|
ThreadContext->Context = NULL;
|
|
|
|
if (status != STATUS_WAIT_0)
|
|
{
|
|
// The operation timed out, or there was an error. Kill the thread. On Vista and above, the
|
|
// thread stack is freed automatically.
|
|
NtTerminateThread(ThreadContext->ThreadHandle, STATUS_UNSUCCESSFUL);
|
|
status = NtWaitForSingleObject(ThreadContext->ThreadHandle, FALSE, NULL);
|
|
NtClose(ThreadContext->ThreadHandle);
|
|
ThreadContext->ThreadHandle = NULL;
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpCallWithTimeoutThreadStart(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext = Parameter;
|
|
|
|
NtSetEvent(threadContext->CompletedEventHandle, NULL);
|
|
|
|
while (TRUE)
|
|
{
|
|
if (NtWaitForSingleObject(threadContext->StartEventHandle, FALSE, NULL) != STATUS_WAIT_0)
|
|
continue;
|
|
|
|
if (threadContext->Routine)
|
|
threadContext->Routine(threadContext->Context);
|
|
|
|
NtSetEvent(threadContext->CompletedEventHandle, NULL);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS PhCallWithTimeout(
|
|
_In_ PUSER_THREAD_START_ROUTINE Routine,
|
|
_In_opt_ PVOID Context,
|
|
_In_opt_ PLARGE_INTEGER AcquireTimeout,
|
|
_In_ PLARGE_INTEGER CallTimeout
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext;
|
|
|
|
if (threadContext = PhpAcquireCallWithTimeoutThread(AcquireTimeout))
|
|
{
|
|
status = PhpCallWithTimeout(threadContext, Routine, Context, CallTimeout);
|
|
PhpReleaseCallWithTimeoutThread(threadContext);
|
|
}
|
|
else
|
|
{
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpCommonQueryObjectRoutine(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
PPHP_QUERY_OBJECT_COMMON_CONTEXT context = Parameter;
|
|
|
|
switch (context->Work)
|
|
{
|
|
case NtQueryObjectWork:
|
|
context->Status = NtQueryObject(
|
|
context->u.NtQueryObject.Handle,
|
|
context->u.NtQueryObject.ObjectInformationClass,
|
|
context->u.NtQueryObject.ObjectInformation,
|
|
context->u.NtQueryObject.ObjectInformationLength,
|
|
context->u.NtQueryObject.ReturnLength
|
|
);
|
|
break;
|
|
case NtQuerySecurityObjectWork:
|
|
context->Status = NtQuerySecurityObject(
|
|
context->u.NtQuerySecurityObject.Handle,
|
|
context->u.NtQuerySecurityObject.SecurityInformation,
|
|
context->u.NtQuerySecurityObject.SecurityDescriptor,
|
|
context->u.NtQuerySecurityObject.Length,
|
|
context->u.NtQuerySecurityObject.LengthNeeded
|
|
);
|
|
break;
|
|
case NtSetSecurityObjectWork:
|
|
context->Status = NtSetSecurityObject(
|
|
context->u.NtSetSecurityObject.Handle,
|
|
context->u.NtSetSecurityObject.SecurityInformation,
|
|
context->u.NtSetSecurityObject.SecurityDescriptor
|
|
);
|
|
break;
|
|
default:
|
|
context->Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS PhpCommonQueryObjectWithTimeout(
|
|
_In_ PPHP_QUERY_OBJECT_COMMON_CONTEXT Context
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
LARGE_INTEGER timeout;
|
|
|
|
timeout.QuadPart = -1 * PH_TIMEOUT_SEC;
|
|
status = PhCallWithTimeout(PhpCommonQueryObjectRoutine, Context, NULL, &timeout);
|
|
|
|
if (NT_SUCCESS(status))
|
|
status = Context->Status;
|
|
|
|
PhFree(Context);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhCallNtQueryObjectWithTimeout(
|
|
_In_ HANDLE Handle,
|
|
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
|
|
_Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation,
|
|
_In_ ULONG ObjectInformationLength,
|
|
_Out_opt_ PULONG ReturnLength
|
|
)
|
|
{
|
|
PPHP_QUERY_OBJECT_COMMON_CONTEXT context;
|
|
|
|
context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT));
|
|
context->Work = NtQueryObjectWork;
|
|
context->Status = STATUS_UNSUCCESSFUL;
|
|
context->u.NtQueryObject.Handle = Handle;
|
|
context->u.NtQueryObject.ObjectInformationClass = ObjectInformationClass;
|
|
context->u.NtQueryObject.ObjectInformation = ObjectInformation;
|
|
context->u.NtQueryObject.ObjectInformationLength = ObjectInformationLength;
|
|
context->u.NtQueryObject.ReturnLength = ReturnLength;
|
|
|
|
return PhpCommonQueryObjectWithTimeout(context);
|
|
}
|
|
|
|
NTSTATUS PhCallNtQuerySecurityObjectWithTimeout(
|
|
_In_ HANDLE Handle,
|
|
_In_ SECURITY_INFORMATION SecurityInformation,
|
|
_Out_writes_bytes_opt_(Length) PSECURITY_DESCRIPTOR SecurityDescriptor,
|
|
_In_ ULONG Length,
|
|
_Out_ PULONG LengthNeeded
|
|
)
|
|
{
|
|
PPHP_QUERY_OBJECT_COMMON_CONTEXT context;
|
|
|
|
context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT));
|
|
context->Work = NtQuerySecurityObjectWork;
|
|
context->Status = STATUS_UNSUCCESSFUL;
|
|
context->u.NtQuerySecurityObject.Handle = Handle;
|
|
context->u.NtQuerySecurityObject.SecurityInformation = SecurityInformation;
|
|
context->u.NtQuerySecurityObject.SecurityDescriptor = SecurityDescriptor;
|
|
context->u.NtQuerySecurityObject.Length = Length;
|
|
context->u.NtQuerySecurityObject.LengthNeeded = LengthNeeded;
|
|
|
|
return PhpCommonQueryObjectWithTimeout(context);
|
|
}
|
|
|
|
NTSTATUS PhCallNtSetSecurityObjectWithTimeout(
|
|
_In_ HANDLE Handle,
|
|
_In_ SECURITY_INFORMATION SecurityInformation,
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor
|
|
)
|
|
{
|
|
PPHP_QUERY_OBJECT_COMMON_CONTEXT context;
|
|
|
|
context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT));
|
|
context->Work = NtSetSecurityObjectWork;
|
|
context->Status = STATUS_UNSUCCESSFUL;
|
|
context->u.NtSetSecurityObject.Handle = Handle;
|
|
context->u.NtSetSecurityObject.SecurityInformation = SecurityInformation;
|
|
context->u.NtSetSecurityObject.SecurityDescriptor = SecurityDescriptor;
|
|
|
|
return PhpCommonQueryObjectWithTimeout(context);
|
|
}
|