diff --git a/trunk/CHANGELOG.txt b/trunk/CHANGELOG.txt index 491af158a..5465225fb 100644 --- a/trunk/CHANGELOG.txt +++ b/trunk/CHANGELOG.txt @@ -12,6 +12,7 @@ Process Hacker * Terminator test: TP1a (TP1, alternative method) * Terminator test: TT1a (TT1, alternative method) * Thread wait analysis now detects NtQueryObject hangs + * Better file object names without KProcessHacker * Small performance improvements * FIXED: * Broken system thread start addresses due to sign-extending diff --git a/trunk/NProcessHacker/NProcessHacker.vcproj b/trunk/NProcessHacker/NProcessHacker.vcproj index 53ba1dc9d..c46ce8d8f 100644 --- a/trunk/NProcessHacker/NProcessHacker.vcproj +++ b/trunk/NProcessHacker/NProcessHacker.vcproj @@ -184,6 +184,10 @@ RelativePath=".\nph.c" > + + @@ -218,6 +222,10 @@ RelativePath=".\nph.h" > + + diff --git a/trunk/NProcessHacker/Release/NProcessHacker.dll b/trunk/NProcessHacker/Release/NProcessHacker.dll index 9317cd04c..a3b590f0f 100644 Binary files a/trunk/NProcessHacker/Release/NProcessHacker.dll and b/trunk/NProcessHacker/Release/NProcessHacker.dll differ diff --git a/trunk/NProcessHacker/nativedefs.h b/trunk/NProcessHacker/nativedefs.h index e5a8eee10..af754046d 100644 --- a/trunk/NProcessHacker/nativedefs.h +++ b/trunk/NProcessHacker/nativedefs.h @@ -3,6 +3,15 @@ #include "nph.h" +typedef enum _OBJECT_INFORMATION_CLASS +{ + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation +} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS; + typedef struct _UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING; typedef struct _CLIENT_ID @@ -38,6 +47,11 @@ typedef struct _UNICODE_STRING PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; +typedef struct _OBJECT_NAME_INFORMATION +{ + UNICODE_STRING Name; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + #define NTDEVICEIOCONTROLFILE_ARGS \ HANDLE FileHandle, \ HANDLE Event, \ @@ -101,6 +115,17 @@ typedef NTSTATUS (NTAPI *_NtOpenThread)( NTOPENTHREAD_ARGS ); +#define NTQUERYOBJECT_ARGS \ + HANDLE Handle, \ + OBJECT_INFORMATION_CLASS ObjectInformationClass, \ + PVOID ObjectInformation, \ + ULONG Length, \ + PULONG ReturnLength + +typedef NTSTATUS (NTAPI *_NtQueryObject)( + NTQUERYOBJECT_ARGS + ); + #define NTREADVIRTUALMEMORY_ARGS \ HANDLE ProcessHandle, \ PVOID BaseAddress, \ diff --git a/trunk/NProcessHacker/nph.c b/trunk/NProcessHacker/nph.c index 220d55008..0d376d8b0 100644 --- a/trunk/NProcessHacker/nph.c +++ b/trunk/NProcessHacker/nph.c @@ -54,6 +54,11 @@ PVOID PhGetProcAddress(PWSTR LibraryName, PSTR ProcName) return GetProcAddress(GetModuleHandle(LibraryName), ProcName); } +VOID PhVoid() +{ + return; +} + BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, @@ -65,6 +70,8 @@ BOOL WINAPI DllMain( case DLL_PROCESS_ATTACH: if (!NT_SUCCESS(PhvInit())) return FALSE; + if (!NT_SUCCESS(PhObjInit())) + return FALSE; if (!NT_SUCCESS(KphInit())) return FALSE; diff --git a/trunk/NProcessHacker/nph.h b/trunk/NProcessHacker/nph.h index 0126f580b..c28aab5ac 100644 --- a/trunk/NProcessHacker/nph.h +++ b/trunk/NProcessHacker/nph.h @@ -63,5 +63,6 @@ NPHAPI PVOID PhAlloc(SIZE_T Size); NPHAPI PVOID PhRealloc(PVOID Memory, SIZE_T Size); NPHAPI VOID PhFree(PVOID Memory); PVOID PhGetProcAddress(PWSTR LibraryName, PSTR ProcName); +NPHAPI VOID PhVoid(); #endif diff --git a/trunk/NProcessHacker/obj.c b/trunk/NProcessHacker/obj.c new file mode 100644 index 000000000..7914ba46f --- /dev/null +++ b/trunk/NProcessHacker/obj.c @@ -0,0 +1,151 @@ +/* + * Process Hacker Library + * + * Copyright (C) 2009 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 "obj.h" + +ULONG PhpQueryFileObjectThreadStart( + PVOID Parameter + ); + +_NtQueryObject NtQueryObject = NULL; + +HANDLE QueryFileObjectThreadHandle = NULL; +PVOID QueryFileObjectFiber = NULL; +CRITICAL_SECTION QueryFileObjectCs; +HANDLE QueryFileObjectStartEvent = NULL; +HANDLE QueryFileObjectCompletedEvent = NULL; +HANDLE QueryFileObjectFileHandle; +PH_QUERY_FILE_OBJECT_BUFFER QueryFileObjectBuffer; + +NTSTATUS PhObjInit() +{ + if (!(NtQueryObject = (_NtQueryObject) + PhGetProcAddress(L"ntdll.dll", "NtQueryObject"))) + return STATUS_PROCEDURE_NOT_FOUND; + + InitializeCriticalSection(&QueryFileObjectCs); + + return STATUS_SUCCESS; +} + +NTSTATUS PhQueryNameFileObject( + HANDLE FileHandle, + POBJECT_NAME_INFORMATION FileObjectNameInformation, + ULONG FileObjectNameInformationLength, + PULONG ReturnLength + ) +{ + ULONG waitResult; + + EnterCriticalSection(&QueryFileObjectCs); + + /* Create a query thread if we don't have one. */ + if (!QueryFileObjectThreadHandle) + { + ULONG threadId; + + QueryFileObjectThreadHandle = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)PhpQueryFileObjectThreadStart, NULL, 0, NULL); + + if (!QueryFileObjectThreadHandle) + { + LeaveCriticalSection(&QueryFileObjectCs); + return STATUS_UNSUCCESSFUL; + } + } + + /* Create the events if they don't exist. */ + if (!QueryFileObjectStartEvent) + if (!(QueryFileObjectStartEvent = CreateEvent(NULL, FALSE, FALSE, NULL))) + return STATUS_UNSUCCESSFUL; + if (!QueryFileObjectCompletedEvent) + if (!(QueryFileObjectCompletedEvent = CreateEvent(NULL, FALSE, FALSE, NULL))) + return STATUS_UNSUCCESSFUL; + + /* Initialize the work context. */ + QueryFileObjectFileHandle = FileHandle; + QueryFileObjectBuffer.Length = FileObjectNameInformationLength; + QueryFileObjectBuffer.Name = FileObjectNameInformation; + /* Allow the worker thread to start. */ + SetEvent(QueryFileObjectStartEvent); + /* Wait for the work to complete, with a timeout of 1 second. */ + waitResult = WaitForSingleObject(QueryFileObjectCompletedEvent, 1000); + + /* Return normally if the work was completed. */ + if (waitResult == WAIT_OBJECT_0) + { + NTSTATUS status; + ULONG returnLength; + + /* Copy the status information before we leave the critical section. */ + status = QueryFileObjectBuffer.Status; + returnLength = QueryFileObjectBuffer.ReturnLength; + LeaveCriticalSection(&QueryFileObjectCs); + + if (ReturnLength) + *ReturnLength = returnLength; + + return status; + } + /* Kill the worker thread if it took too long. */ + else if (waitResult == WAIT_TIMEOUT) + { + /* Kill the thread. */ + if (TerminateThread(QueryFileObjectThreadHandle, 1)) + { + QueryFileObjectThreadHandle = NULL; + + /* Delete the fiber (and free the thread stack). */ + DeleteFiber(QueryFileObjectFiber); + QueryFileObjectFiber = NULL; + } + + LeaveCriticalSection(&QueryFileObjectCs); + return STATUS_UNSUCCESSFUL; + } +} + +ULONG PhpQueryFileObjectThreadStart( + PVOID Parameter + ) +{ + QueryFileObjectFiber = ConvertThreadToFiber(Parameter); + + while (TRUE) + { + /* Wait for work. */ + if (WaitForSingleObject(QueryFileObjectStartEvent, INFINITE) != WAIT_OBJECT_0) + continue; + + QueryFileObjectBuffer.Status = NtQueryObject( + QueryFileObjectFileHandle, + ObjectNameInformation, + QueryFileObjectBuffer.Name, + QueryFileObjectBuffer.Length, + &QueryFileObjectBuffer.ReturnLength + ); + + /* Work done. */ + SetEvent(QueryFileObjectCompletedEvent); + } + + return 0; +} diff --git a/trunk/NProcessHacker/obj.h b/trunk/NProcessHacker/obj.h new file mode 100644 index 000000000..49a26c473 --- /dev/null +++ b/trunk/NProcessHacker/obj.h @@ -0,0 +1,45 @@ +/* + * Process Hacker Library + * + * Copyright (C) 2009 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 . + */ + +#ifndef _OBJ_H +#define _OBJ_H + +#include "nph.h" +#include "nativedefs.h" + +typedef struct _PH_QUERY_FILE_OBJECT_BUFFER +{ + NTSTATUS Status; + ULONG Length; + ULONG ReturnLength; + POBJECT_NAME_INFORMATION Name; +} PH_QUERY_FILE_OBJECT_BUFFER, *PPH_QUERY_FILE_OBJECT_BUFFER; + +NTSTATUS PhObjInit(); + +NPHAPI NTSTATUS PhQueryNameFileObject( + HANDLE FileHandle, + POBJECT_NAME_INFORMATION FileObjectNameInformation, + ULONG FileObjectNameInformationLength, + PULONG ReturnLength + ); + +#endif diff --git a/trunk/ProcessHacker.Native/Api/Extensions.cs b/trunk/ProcessHacker.Native/Api/Extensions.cs index c6e7aea9d..c2dd19390 100644 --- a/trunk/ProcessHacker.Native/Api/Extensions.cs +++ b/trunk/ProcessHacker.Native/Api/Extensions.cs @@ -61,6 +61,52 @@ namespace ProcessHacker.Native.Api } } + private static string GetObjectNameNt(ProcessHandle process, IntPtr handle, GenericHandle dupHandle) + { + int retLength; + int baseAddress = 0; + + if (KProcessHacker.Instance != null) + { + KProcessHacker.Instance.ZwQueryObject(process, handle, ObjectInformationClass.ObjectNameInformation, + IntPtr.Zero, 0, out retLength, out baseAddress); + } + else + { + Win32.NtQueryObject(dupHandle, ObjectInformationClass.ObjectNameInformation, + IntPtr.Zero, 0, out retLength); + } + + if (retLength > 0) + { + using (MemoryAlloc oniMem = new MemoryAlloc(retLength)) + { + if (KProcessHacker.Instance != null) + { + if (KProcessHacker.Instance.ZwQueryObject(process, handle, ObjectInformationClass.ObjectNameInformation, + oniMem, oniMem.Size, out retLength, out baseAddress) >= NtStatus.Error) + throw new Exception("ZwQueryObject failed."); + } + else + { + if (Win32.NtQueryObject(dupHandle, ObjectInformationClass.ObjectNameInformation, + oniMem, oniMem.Size, out retLength) >= NtStatus.Error) + throw new Exception("NtQueryObject failed."); + } + + var oni = oniMem.ReadStruct(); + var str = oni.Name; + + if (KProcessHacker.Instance != null) + str.Buffer = str.Buffer.Increment(-baseAddress + oniMem); + + return str.Read(); + } + } + + throw new Exception("NtQueryObject failed."); + } + public static ObjectInformation GetHandleInfo(this SystemHandleInformation thisHandle) { using (ProcessHandle process = new ProcessHandle(thisHandle.ProcessId, @@ -147,77 +193,65 @@ namespace ProcessHacker.Native.Api } } - if (KProcessHacker.Instance != null && info.TypeName == "File") + // Get the object's name. If the object is a file we must take special + // precautions so that we don't hang. + if (info.TypeName == "File") { - // use KProcessHacker for files - info.OrigName = KProcessHacker.Instance.GetFileObjectName(thisHandle); - } - else if (info.TypeName == "File" && (int)thisHandle.GrantedAccess == 0x0012019f) - { - // KProcessHacker not available, fall back to using hack (i.e. not querying the name at all) - } - else - { - int baseAddress = 0; - if (KProcessHacker.Instance != null) { - KProcessHacker.Instance.ZwQueryObject(process, handle, ObjectInformationClass.ObjectNameInformation, - IntPtr.Zero, 0, out retLength, out baseAddress); + // Use KProcessHacker for files to avoid hangs. + info.OrigName = KProcessHacker.Instance.GetFileObjectName(thisHandle); } else { - Win32.NtQueryObject(objectHandle, ObjectInformationClass.ObjectNameInformation, - IntPtr.Zero, 0, out retLength); - } - - if (retLength > 0) - { - using (MemoryAlloc oniMem = new MemoryAlloc(retLength)) + try { - if (KProcessHacker.Instance != null) + // Use NProcessHacker. + using (MemoryAlloc oniMem = new MemoryAlloc(0x4000)) { - if (KProcessHacker.Instance.ZwQueryObject(process, handle, ObjectInformationClass.ObjectNameInformation, - oniMem, oniMem.Size, out retLength, out baseAddress) >= NtStatus.Error) - throw new Exception("ZwQueryObject failed."); + if (NProcessHacker.PhQueryNameFileObject( + objectHandle, oniMem, oniMem.Size, out retLength) >= NtStatus.Error) + throw new Exception("PhQueryNameFileObject failed."); + + var oni = oniMem.ReadStruct(); + + info.OrigName = oni.Name.Read(); } - else - { - if (Win32.NtQueryObject(objectHandle, ObjectInformationClass.ObjectNameInformation, - oniMem, oniMem.Size, out retLength) >= NtStatus.Error) - throw new Exception("NtQueryObject failed."); - } - - var oni = oniMem.ReadStruct(); - var str = oni.Name; - - if (KProcessHacker.Instance != null) - str.Buffer = str.Buffer.Increment(-baseAddress + oniMem); - - info.OrigName = str.Read(); + } + catch (DllNotFoundException) + { + // KProcessHacker and NProcessHacker not available. Fall back to using hack + // (i.e. not querying the name at all if the access is 0x0012019f) + if ((int)thisHandle.GrantedAccess != 0x0012019f) + info.OrigName = GetObjectNameNt(process, handle, objectHandle); } } } + else + { + // Not a file. Query the object normally. + info.OrigName = GetObjectNameNt(process, handle, objectHandle); + } - // get a better name for the handle + // Get a better name for the handle. try { switch (info.TypeName) { case "File": - // resolves \Device\Harddisk1 into C:, for example + // Resolves \Device\Harddisk1 into C:, for example. info.BestName = FileUtils.DeviceFileNameToDos(info.OrigName); break; case "Key": - string hklmString = "\\registry\\machine"; - string hkcrString = "\\registry\\machine\\software\\classes"; + const string hklmString = "\\registry\\machine"; + const string hkcrString = "\\registry\\machine\\software\\classes"; string hkcuString = "\\registry\\user\\" + System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString().ToLower(); string hkcucrString = "\\registry\\user\\" + System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString().ToLower() + "_classes"; - string hkuString = "\\registry\\user"; + const string hkuString = "\\registry\\user"; if (info.OrigName.ToLower().StartsWith(hkcrString)) info.BestName = "HKCR" + info.OrigName.Substring(hkcrString.Length); diff --git a/trunk/ProcessHacker/Program/NProcessHacker.cs b/trunk/ProcessHacker.Native/NProcessHacker.cs similarity index 67% rename from trunk/ProcessHacker/Program/NProcessHacker.cs rename to trunk/ProcessHacker.Native/NProcessHacker.cs index 0c8f85e6d..144663b7d 100644 --- a/trunk/ProcessHacker/Program/NProcessHacker.cs +++ b/trunk/ProcessHacker.Native/NProcessHacker.cs @@ -24,8 +24,9 @@ using System.Runtime.InteropServices; using System.Windows.Forms; using ProcessHacker.Native; using ProcessHacker.Native.Api; +using System; -namespace ProcessHacker +namespace ProcessHacker.Native { public class NProcessHacker { @@ -55,22 +56,33 @@ namespace ProcessHacker [DllImport("nprocesshacker.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)] public static extern NtStatus PhpQueryProcessWs( - int ProcessHandle, - WS_INFORMATION_CLASS WsInformationClass, - out int WsInformation, - int WsInformationLength, - out int ReturnLength + [In] IntPtr ProcessHandle, + [In] WS_INFORMATION_CLASS WsInformationClass, + [Out] out int WsInformation, + [In] int WsInformationLength, + [Out] out int ReturnLength ); [DllImport("nprocesshacker.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)] public static extern NtStatus PhpQueryProcessWs( - int ProcessHandle, - WS_INFORMATION_CLASS WsInformationClass, - out WS_ALL_COUNTS WsInformation, - int WsInformationLength, - out int ReturnLength + [In] IntPtr ProcessHandle, + [In] WS_INFORMATION_CLASS WsInformationClass, + [Out] out WS_ALL_COUNTS WsInformation, + [In] int WsInformationLength, + [Out] out int ReturnLength ); + [DllImport("nprocesshacker.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)] + public static extern NtStatus PhQueryNameFileObject( + [In] IntPtr FileHandle, + [In] IntPtr FileObjectNameInformation, + [In] int FileObjectNameInformationLength, + [Out] [Optional] out int ReturnLength + ); + + [DllImport("nprocesshacker.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void PhVoid(); + [DllImport("nprocesshacker.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public static extern VerifyResult PhvVerifyFile(string FileName); } diff --git a/trunk/ProcessHacker.Native/ProcessHacker.Native.csproj b/trunk/ProcessHacker.Native/ProcessHacker.Native.csproj index 447dc3acb..22056976b 100644 --- a/trunk/ProcessHacker.Native/ProcessHacker.Native.csproj +++ b/trunk/ProcessHacker.Native/ProcessHacker.Native.csproj @@ -59,6 +59,7 @@ + diff --git a/trunk/ProcessHacker/Components/ProcessTree/ProcessNode.cs b/trunk/ProcessHacker/Components/ProcessTree/ProcessNode.cs index f768a82c1..3c3f5ba60 100644 --- a/trunk/ProcessHacker/Components/ProcessTree/ProcessNode.cs +++ b/trunk/ProcessHacker/Components/ProcessTree/ProcessNode.cs @@ -25,6 +25,7 @@ using System.Collections.Generic; using System.Drawing; using Aga.Controls.Tree; using ProcessHacker.Common; +using ProcessHacker.Native; using ProcessHacker.Native.Api; using ProcessHacker.Native.Objects; using ProcessHacker.Native.Security; diff --git a/trunk/ProcessHacker/ProcessHacker.csproj b/trunk/ProcessHacker/ProcessHacker.csproj index 627f7f613..96813b1a1 100644 --- a/trunk/ProcessHacker/ProcessHacker.csproj +++ b/trunk/ProcessHacker/ProcessHacker.csproj @@ -705,7 +705,6 @@ -