/* * 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 . */ #include #include #include #include #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); }