-3

Not sure if it's possible to get such information, but i was messing with the WinAPI docs trying to figure out a way to get the number of opened handles to my process. First i created a console application that retrieves the number of handles the current process has, then i created another console app that gets a handle to the first app with an OpenProcess call. The code used in the first application is the following:

auto hProcess = GetCurrentProcess();
while (true)
{
    DWORD Handles = 0;
    if (GetProcessHandleCount(hProcess, &Handles))
    {
        std::cout << Handles << '\n';
    }
    Sleep(2000);
}

I was expecting the first application to print 1, but i got 50 when i ran in my Windows 10 PC, and 58 when i ran it with elevated privilegies. Also, when i ran the code above in a virtual machine with Windows 7, 11 handles were printed. What is going on?

async
  • 1
  • 1
  • 2
  • 1
    `GetProcessHandleCount()` tells you how many handles the process itself has opened (all types of kernel handles), not how many handles to your process other processes have opened. – Jonathan Potter Dec 25 '19 at 21:21
  • yes, this easy get count of opened handles to some process, but require native api. and need execute native code - so your code need be 64bit in x64 windows – RbMm Dec 25 '19 at 21:23
  • Every process has a object table that contains handle entries for NT executive objects in kernel space. These objects are instances of executive types (e.g. WindowStation, Desktop, Section, Job, Process, Thread, Event, and File). Note that User and GDI objects (e.g. window handles) are not executive objects, so they are not included in the handle count. The associated handle value is an opaque reference to the executive object, which is valid only in the associated process context. A handle entry also contains the access granted to the object and flags such as whether the handle can be closed. – Eryk Sun Dec 25 '19 at 22:01
  • In Windows 8.1+ with just the Windows API, it's possible to snapshot the handle table of a particular process via [process snapshotting](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/proc_snap/overview-of-process-snapshotting), from which you can count the number of handles the process has for a given process. However, getting a snapshot of the handle tables of all running processes in a single call requires the native NT API. – Eryk Sun Dec 25 '19 at 22:16
  • what is sense in your task ? assume you got number open handles of your process - and what this give you ? this task can have sense only for some system-level tools, but here need not only number of handles, but list of processes, which open handle to process, access, etc – RbMm Dec 26 '19 at 11:30
  • First of all, thanks for all the hints! @RbMm Actually, i was trying to find a way to prevent other processes from getting a handle to mine, so my idea was count the number of handles to my process exists and close my process if handleCount > 0, but looks like there is not an easy way to accomplish this. – async Dec 26 '19 at 18:43
  • @ErykSun So i would have to snapshot the process list, iterate over it and check if the nth process has a handle to mine? – async Dec 26 '19 at 18:51
  • @async - here [correct code](https://pastebin.com/9d8UB1TV) for get all opened handles to your process. but note that always will be several opened to your process handles – RbMm Dec 26 '19 at 19:48
  • @RbMm Thanks, but why there will always be several handles opened to my process? – async Dec 26 '19 at 21:57
  • @async - because system processes, say *csrss.exe* will be have your process handles. execute my code and dump result (process id which have opened your process) if want better understand – RbMm Dec 26 '19 at 22:04
  • @async, to do this with the Windows API, yes, you would have to call `PssCaptureSnapshot` individually on every process of interest, which would be inherently prone to race conditions. – Eryk Sun Dec 27 '19 at 01:28
  • In other words, what you are *really* trying to do is re-implement [access control lists](https://learn.microsoft.com/en-us/windows/win32/secauthz/access-control-lists). – IInspectable Dec 27 '19 at 09:05

2 Answers2

0

First, GetProcessHandleCount returns the number of HANDLEs that the calling process has opened. Second, it is slightly unreliable.

GetProcessHandleCount will return the number of HANDLEs AFTER the object table has been initialized by the kernel and your process has started up. For different versions of Windows, the number of already-existing handles will vary, depending on how the kernel is designed. Some of these handles are handles to known DLL directories, HYPORVISOR_SHARED_DATA, etc. Once all the 'base' handles are created and the process's main is called, GetProcessHandleCount returns the number of opened handles from that point.

So the number of actual HANDLEs in the process's object table is slightly off using GetProcessHandleCount. The reason why you get something above 1 is because even after the kernel has initialized the object table, the CRT will open files and memory sections, which return HANDLEs

I think you were expecting 1 because of the call to GetCurrentProcess(). But that returns (HANDLE)-1, which is not a real HANDLE.

If you want the real count of the number of HANDLE's your process has, use NtQuerySystemInformation and loop for open HANDLE's with your process's ID. (Side note, GetProcessHandleCount calls NtQueryInformationProcess.)

Arush Agarampur
  • 1,340
  • 7
  • 20
  • I was expecting 1 because my second console app opened a handle to the first one. – async Dec 26 '19 at 18:56
  • @RbMm - I hope this is not too off topic, but do you know why `NtQuerySystemInformation` returns an incorrect buffer size and why we need to loop `RtlAllocateHeap` and `RtlFreeHeap`? – Arush Agarampur Dec 27 '19 at 20:26
  • `NtQuerySystemInformation` return incorrect buffer size (`STATUS_INFO_LENGTH_MISMATCH`) if you passed too small buffer to call. really - we can not know how many memory need for store all handles information. api return **hint** - how many memory need for store all information. but after you allocate such memory and call again - can be new handles opened in system and new buffer again will be too small. so really we need call this api in loop, until we not pass big enough buffer. on practice will be no more 2-3 itterations – RbMm Dec 27 '19 at 21:04
  • 1
    `GetCurrentProcess` returns `(HANDLE)-1`, not `(DWORD)-1`. The values are the same in a 32-bit process, but `HANDLE` is 8 bytes in a 64-bit process. It has to be the same size as a pointer. While Kernel and User/GDI handle values are 32-bit, even in 64-bit Windows, some handles are actually pointers. For example, the "find" handle from `FindFirstFileExW` is a pointer to a record that contains a file handle, a buffer and state for use with `NtQueryDirectoryFileEx`. The `FindClose` routine closes the file handle and deallocates the buffer and find record. – Eryk Sun Dec 27 '19 at 21:29
  • Do not call `NtQuerySystemInformation`: `SystemHandleInformation` to merely get the handle count for a single process. `GetProcessHandleCount` (i.e. `NtQueryInformationProcess`: `ProcessHandleCount`) is the correct API function for this case. – Eryk Sun Dec 27 '19 at 21:41
  • Well, by my reading, this is not right: "If you want the real count of the number of HANDLE's your process has, use NtQuerySystemInformation and loop for open HANDLE's with your process's ID." You seem to be positioning this as a more accurate approach than `GetProcessHandleCount` for the purpose of counting the number of executive handles allocated in the current process. It is not. On the other hand, `NtQuerySystemInformation` is required for a race-free tally of handles allocated *for* the current process object across all processes in the system, as RbMm has demonstrated in a code sample. – Eryk Sun Dec 27 '19 at 23:36
0

The method of enumerating handle has been given in the comments. All I have done is to provide a code reference.

You may need to traverse all process names and print the required pid.

Traversal process:

#include <stdio.h>
#include <Windows.h>
#include <winternl.h>
#include <cstringt.h>
#include <winternl.h>
#include <string>
#include <wchar.h>

#pragma comment(lib,"ntdll.lib") // Need to link with ntdll.lib import library. You can find the ntdll.lib from the Windows DDK.

typedef struct _SYSTEM_PROCESS_INFO
{
    ULONG                   NextEntryOffset;
    ULONG                   NumberOfThreads;
    LARGE_INTEGER           Reserved[3];
    LARGE_INTEGER           CreateTime;
    LARGE_INTEGER           UserTime;
    LARGE_INTEGER           KernelTime;
    UNICODE_STRING          ImageName;
    ULONG                   BasePriority;
    HANDLE                  ProcessId;
    HANDLE                  InheritedFromProcessId;
}SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;



int main()
{
    NTSTATUS status;
    PVOID buffer;
    PSYSTEM_PROCESS_INFO spi;

    buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // We need to allocate a large buffer because the process list can be large.

    if (!buffer)
    {
        printf("\nError: Unable to allocate memory for process list (%d)\n", GetLastError());
        return -1;
    }

    printf("\nProcess list allocated at address %#x\n", buffer);
    spi = (PSYSTEM_PROCESS_INFO)buffer;

    if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemProcessInformation, spi, 1024 * 1024, NULL)))
    {
        printf("\nError: Unable to query process list (%#x)\n", status);

        VirtualFree(buffer, 0, MEM_RELEASE);
        return -1;
    }

    while (spi->NextEntryOffset) // Loop over the list until we reach the last entry.
    {
        const wchar_t* str = L"Test.exe"; //The process name you specified
        spi = (PSYSTEM_PROCESS_INFO)((LPBYTE)spi + spi->NextEntryOffset); // Calculate the address of the next entry.
        if (wcscmp(spi->ImageName.Buffer, str) == 0)
        {
            printf("\nProcess name: %ws | Process ID: %d\n", spi->ImageName.Buffer, spi->ProcessId); // Display process information.            
        }
    }


    printf("\nPress any key to continue.\n");
    getchar();

    VirtualFree(buffer, 0, MEM_RELEASE); // Free the allocated buffer.

    return 0;
}

Then according to the given PID, use NT API to enumerate the handles.

Enumerate handles:

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <wchar.h>
#include <stdlib.h>

#pragma comment(lib,"ntdll.lib")

#define NT_SUCCESS(x) ((x) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004

#define SystemHandleInformation 16
#define ObjectBasicInformation 0
#define ObjectNameInformation 1
#define ObjectTypeInformation 2

static int num = 0;


typedef NTSTATUS(NTAPI *_NtQuerySystemInformation)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
    );
typedef NTSTATUS(NTAPI *_NtDuplicateObject)(
    HANDLE SourceProcessHandle,
    HANDLE SourceHandle,
    HANDLE TargetProcessHandle,
    PHANDLE TargetHandle,
    ACCESS_MASK DesiredAccess,
    ULONG Attributes,
    ULONG Options
    );
typedef NTSTATUS(NTAPI *_NtQueryObject)(
    HANDLE ObjectHandle,
    ULONG ObjectInformationClass,
    PVOID ObjectInformation,
    ULONG ObjectInformationLength,
    PULONG ReturnLength
    );

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _SYSTEM_HANDLE
{
    ULONG ProcessId;
    BYTE ObjectTypeNumber;
    BYTE Flags;
    USHORT Handle;
    PVOID Object;
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
    ULONG HandleCount;
    SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef enum _POOL_TYPE
{
    NonPagedPool,
    PagedPool,
    NonPagedPoolMustSucceed,
    DontUseThisType,
    NonPagedPoolCacheAligned,
    PagedPoolCacheAligned,
    NonPagedPoolCacheAlignedMustS
} POOL_TYPE, *PPOOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION
{
    UNICODE_STRING Name;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccess;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    USHORT MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG PagedPoolUsage;
    ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

typedef struct _SYSTEM_PROCESS_INFO
{
    ULONG                   NextEntryOffset;
    ULONG                   NumberOfThreads;
    LARGE_INTEGER           Reserved[3];
    LARGE_INTEGER           CreateTime;
    LARGE_INTEGER           UserTime;
    LARGE_INTEGER           KernelTime;
    UNICODE_STRING          ImageName;
    ULONG                   BasePriority;
    HANDLE                  ProcessId;
    HANDLE                  InheritedFromProcessId;
}SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
    return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}

int wmain(int argc, WCHAR *argv[])
{
    _NtQuerySystemInformation NtQuerySystemInformation =
        (_NtQuerySystemInformation)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR)"NtQuerySystemInformation");
    _NtDuplicateObject NtDuplicateObject =
        (_NtDuplicateObject)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR) "NtDuplicateObject");
    _NtQueryObject NtQueryObject =
        (_NtQueryObject)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR)"NtQueryObject");
    NTSTATUS status;
    PSYSTEM_HANDLE_INFORMATION handleInfo;
    ULONG handleInfoSize = 0x10000;
    ULONG pid;
    HANDLE processHandle;
    ULONG i;

    pid = 10228; //PID you get

    if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid)))
    {
        printf("Could not open PID %d! (Don't try to open a system process.)\n", pid);
        return 1;
    }

    handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);

    /* NtQuerySystemInformation won't give us the correct buffer size,
       so we guess by doubling the buffer size. */
    while ((status = NtQuerySystemInformation(
        SystemHandleInformation,
        handleInfo,
        handleInfoSize,
        NULL
    )) == STATUS_INFO_LENGTH_MISMATCH)
        handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);

    /* NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH. */
    if (!NT_SUCCESS(status))
    {
        printf("NtQuerySystemInformation failed!\n");
        return 1;
    }

    for (i = 0; i < handleInfo->HandleCount; i++)
    {
        SYSTEM_HANDLE handle = handleInfo->Handles[i];
        HANDLE dupHandle = NULL;
        POBJECT_TYPE_INFORMATION objectTypeInfo;
        PVOID objectNameInfo;
        UNICODE_STRING objectName;
        ULONG returnLength;

        /* Check if this handle belongs to the PID the user specified. */
        if (handle.ProcessId != pid)
            continue;

        /* Duplicate the handle so we can query it. */
        if (!NT_SUCCESS(NtDuplicateObject(
            processHandle,
            (HANDLE)handle.Handle,
            GetCurrentProcess(),
            &dupHandle,
            0,
            0,
            0
        )))
        {
            printf("[%#x] Error!\n", handle.Handle);
            num++;
            continue;
        }

        /* Query the object type. */
        objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
        if (!NT_SUCCESS(NtQueryObject(
            dupHandle,
            ObjectTypeInformation,
            objectTypeInfo,
            0x1000,
            NULL
        )))
        {       
            printf("[%#x] Error!\n", handle.Handle);
            num++;
            CloseHandle(dupHandle);
            continue;
        }

        /* Query the object name (unless it has an access of
           0x0012019f, on which NtQueryObject could hang. */
        if (handle.GrantedAccess == 0x0012019f)
        {
            /* We have the type, so display that. */
            printf(
                "[%#x] %.*S: (did not get name)\n",
                handle.Handle,
                objectTypeInfo->Name.Length / 2,
                objectTypeInfo->Name.Buffer
            );
            num++;
            free(objectTypeInfo);
            CloseHandle(dupHandle);
            continue;
        }

        objectNameInfo = malloc(0x1000);
        if (!NT_SUCCESS(NtQueryObject(
            dupHandle,
            ObjectNameInformation,
            objectNameInfo,
            0x1000,
            &returnLength
        )))
        {
            /* Reallocate the buffer and try again. */
            objectNameInfo = realloc(objectNameInfo, returnLength);
            if (!NT_SUCCESS(NtQueryObject(
                dupHandle,
                ObjectNameInformation,
                objectNameInfo,
                returnLength,
                NULL
            )))
            {
                /* We have the type name, so just display that. */
                printf(
                    "[%#x] %.*S: (could not get name)\n",
                    handle.Handle,
                    objectTypeInfo->Name.Length / 2,
                    objectTypeInfo->Name.Buffer
                );
                num++;
                free(objectTypeInfo);
                free(objectNameInfo);
                CloseHandle(dupHandle);
                continue;
            }
        }

        /* Cast our buffer into an UNICODE_STRING. */
        objectName = *(PUNICODE_STRING)objectNameInfo;

        /* Print the information! */
        if (objectName.Length)
        {
            /* The object has a name. */
            printf(
                "[%#x] %.*S: %.*S\n",
                handle.Handle,
                objectTypeInfo->Name.Length / 2,
                objectTypeInfo->Name.Buffer,
                objectName.Length / 2,
                objectName.Buffer
            );
            num++;
        }
        else
        {
            /* Print something else. */
            printf(
                "[%#x] %.*S: (unnamed)\n",
                handle.Handle,
                objectTypeInfo->Name.Length / 2,
                objectTypeInfo->Name.Buffer
            );
            num++;
        }

        free(objectTypeInfo);
        free(objectNameInfo);
        CloseHandle(dupHandle);
    }

    free(handleInfo);
    CloseHandle(processHandle);
    printf("Enumerate handles:  %d\n", num);
    getchar();
    return 0;
}

I've tested the two code samples and they can work well.

Useful reference links for you:

Strive Sun
  • 5,988
  • 1
  • 9
  • 26
  • your solution **very not optimal**. you need open handle to self process **before** enumerate system handles. and first find self open handle, from it - we get address of process object. and then search for handles with this address. any `NtQueryObject` not need at all – RbMm Dec 26 '19 at 11:19
  • and implementation also very not optimal. say in call `NtQuerySystemInformation` - last parameter - must not be 0 but give you hint - how many memory need for store information. need use this instead `handleInfoSize *= 2` - `realloc` - this is bad solution. need use free/malloc only here. realloc really **copy** memory context to new location, which absolute not need in concrete case. etc – RbMm Dec 26 '19 at 11:25
  • The OP wants to know whether another process has a handle to the current process, not to dump the handle table of a given process. – Eryk Sun Dec 27 '19 at 01:17
  • This is weird: `GrantedAccess == 0x0012019f` (i.e. `FILE_GENERIC_READ | FILE_GENERIC_WRITE`). It attempts to avoid a potentially blocking name query for file objects. The file type implements a custom `ObjectNameInformation` routine that's synchronous, which is problematic for a synchronous-mode file in which the internal lock is already acquired. But this problem can occur with nearly *any access* (e.g. an inbound pipe opened with generic read access could be blocked in a read). The only way to address it reliably is by spawning a query thread that gets killed or reset after a given timeout. – Eryk Sun Dec 27 '19 at 01:18
  • @ErykSun - really we not need here all this and have problems query file names with synchronous io and pending request. much more simply - https://pastebin.com/9d8UB1TV – RbMm Dec 27 '19 at 21:48
  • 1
    @RbMm, I know, that's right, but your code is doing what the OP *asked for*, as opposed to dumping the whole object table of a given process. My comment above was regarding the implementation of the latter (unrequested behavior) and the false assumption that only objects, and in particular file objects, with `0x0012019f` granted access are problematic with regard to a name query. I've seen this passed around online for years. People keep repeating it, apparently without thinking about how nonsensical it is. – Eryk Sun Dec 27 '19 at 23:27
  • @ErykSun - sorry for unrelated question, but what you think about [this](https://community.osr.com/discussion/291804/when-will-be-queued-a-packet-to-the-i-o-completion-port) ? question about how exactly determinate when be or not IOCP notification for asynchronous I/O (when file binded to IOCP) – RbMm Dec 28 '19 at 08:56
  • @RbMm, in the thread at the OSR forum, I think you're correct, but the experts there know more than I do on this subject. ISTM that for a fast lock-control request, the I/O manager should only queue a minipacket to the port if the routine succeeds with a non-error status. Consider that when the fast path isn't possible, the request takes the long way with an I/O request packet, in which case the lock-control routine may immediately complete the request with an error and return this synchronously instead of pending the request, and thus the I/O completion APC won't queue the packet to the port. – Eryk Sun Dec 29 '19 at 21:48
  • @ErykSun i random discovery error with NtLockFile (STATUS_LOCK_NOT_GRANTED with callback) initially i looked only wrk src code and understand why this. and decide that the same problem must be and with IOCTL if we use asynchronous file and driver implement FastIoDeviceControl and return TRUE from it with NT_ERROR status. this already more serrious. but when i test IOCTL in win10 - no error. so i look for binary code (win7,8.1,10 ) and understand that error in IopXxxControlFile was fixed in 8.1 how minimum. but ms forget fix NtLockFile. so bug is only here. easy for fix, but still exist – RbMm Dec 29 '19 at 22:07