Search code examples
c++cwindowswinapigdi

How to get list of GDI handles


I'm trying to write, using DLL injection method, application which displays bitmaps used by another program and I want to get for this specific process list of GDI Handles which it is using (list like in GDIView.exe utility). I found article about NtQuerySystemInformation function, but this description only works with handles to Kernel Objects. Can somebody help?


Solution

  • Here is a console application code that dumps all GDI handles out for a given process identifier. It should compile and work fine for 32 or 64-bit applications, as well as 32-bit application running on 64-bit OSes. It uses a lot of undocumented functions so don't rely on it :-) Credits on structures for the shared GDI table goes to Feng Yuan original work. (I had to adapt it for WOW64).

    Here is a sample output when ran on a Notepad (64-bit) process:

    [C:\Temp\EnumGdi\Debug]EnumGdi.exe 5916
    DC handle:0xF20105DB
    Bitmap handle:0xDF05118B
    Font handle:0xDC0A19E0
    Font handle:0xAB0A1A62
    DC handle:0xA3011A63
    Region handle:0xAF041B7B
    Brush handle:0x11101C5B
    Font handle:0x280A1CA1
    Font handle:0xBB0A1D13
    Bitmap handle:0xA3051DD8
    Font handle:0xB40A1DDC
    Region handle:0x3A041EE4
    Brush handle:0x0B101F04
    Region handle:0xC6041F3D
    Font handle:0x2C0A2384
    Brush handle:0xBA1024DA
    

    EnumGdi.cpp:

    #include "stdafx.h"
    #include "enumgdi.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {   
        if (argc < 2)
        {
            printf("Format is EnumGdi <process id>\n");
            return 0;
        }
    
        // get process identifier
        DWORD dwId = _wtoi(argv[1]);
    
        // open the process
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwId);
        DWORD err = 0;
        if (hProcess == NULL)
        {
            printf("OpenProcess %u failed\n", dwId);
            err = GetLastError();
            return -1;
        }
    
        // determine if 64 or 32-bit processor
        SYSTEM_INFO si;
        GetNativeSystemInfo(&si);
    
        // NOTE: as this is undocumented, it *may vary* depending on bitness (32/64) and on Windows version.
        // use WinDbg "dt ntdll!_PEB" command and search for GdiSharedHandleTable offset to find the truth out
        DWORD GdiSharedHandleTableOffset  = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 0xF8 : 0x94;
        DWORD tableCount = 16384; // count of GDI table cells
    
        // determine if this process is running on WOW64
        BOOL wow;
        IsWow64Process(GetCurrentProcess(), &wow);
    
        // read basic info to get PEB address, we only need the beginning of PEB
        DWORD pebSize = GdiSharedHandleTableOffset + 8;
        LPBYTE peb = (LPBYTE)malloc(pebSize);
        ZeroMemory(peb, pebSize);
    
        if (wow)
        {
            // we're running as a 32-bit process in a 64-bit process
            PROCESS_BASIC_INFORMATION_WOW64 pbi;
            ZeroMemory(&pbi, sizeof(pbi));
    
            // get process information from 64-bit world
            _NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtWow64QueryInformationProcess64");
            err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
            if (err != 0)
            {
                printf("NtWow64QueryInformationProcess64 failed\n");
                CloseHandle(hProcess);
                return -1;
            }
    
            // read PEB from 64-bit address space
            _NtWow64ReadVirtualMemory64 read = (_NtWow64ReadVirtualMemory64)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtWow64ReadVirtualMemory64");
            err = read(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL);
            if (err != 0)
            {
                printf("NtWow64ReadVirtualMemory64 PEB failed\n");
                CloseHandle(hProcess);
                return -1;
            }
    
            // get GDI table ptr from PEB
            GDICELL_WOW64* gdiTable = (GDICELL_WOW64*)*(LPVOID*)(peb + GdiSharedHandleTableOffset); // address in remote process adress space
            if (gdiTable == NULL)
            {
                printf("GDI32.DLL is not loaded in the process\n");
                CloseHandle(hProcess);
                return -1;
            }
            free(peb);
            DWORD tableSize = sizeof(GDICELL_WOW64) * tableCount; // size of GDI table
            GDICELL_WOW64* table = (GDICELL_WOW64*)malloc(tableSize); // local table copied over to our address space
    
            // copy GDI table
            err = read(hProcess, gdiTable, table, tableSize, NULL);
            if (err != 0)
            {
                printf("NtWow64ReadVirtualMemory64 GdiTable failed\n");
                free(table);
                CloseHandle(hProcess);
                return -1;
            }
    
            for(DWORD i = 0; i < tableCount; i++)
            {
                GDICELL_WOW64 cell = table[i];
                if (cell.wProcessId != dwId)
                    continue;
    
                HGDIOBJ gdiHandle = (HGDIOBJ)((cell.wUpper << 16) + i);
                WORD type = cell.wType & 0x7F;
                switch(type)
                {
                    case 1:
                        printf("DC handle:0x%08X\n", gdiHandle );
                        break;
    
                    case 4:
                        printf("Region handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 5:
                        printf("Bitmap handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 8:
                        printf("Palette handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 10:
                        printf("Font handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 16:
                        printf("Brush handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 48:
                        printf("Pen handle:0x%08X\n", gdiHandle);
                        break;
    
                    default:
                        printf("Unknown type handle:0x%08X\n", gdiHandle);
                        break;
                }
            }
            free(table);
        }
        else
        {
            // we're running as a 32-bit process in a 32-bit OS, or as a 64-bit process in a 64-bit OS
            PROCESS_BASIC_INFORMATION pbi;
            ZeroMemory(&pbi, sizeof(pbi));
    
            // get process information
            _NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
            err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
            if (err != 0)
            {
                printf("NtQueryInformationProcess failed\n");
                CloseHandle(hProcess);
                return -1;
            }
    
            // read PEB
            _NtReadVirtualMemory read = (_NtReadVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtReadVirtualMemory");
            err = read(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL);
            if (err != 0)
            {
                printf("NtReadVirtualMemory PEB failed\n");
                CloseHandle(hProcess);
                return -1;
            }
    
            // get GDI table ptr
            GDICELL* gdiTable = (GDICELL*)*(LPVOID*)(peb + GdiSharedHandleTableOffset); // address in remote process adress space
            if (gdiTable == NULL)
            {
                printf("GDI32.DLL is not loaded in the process\n");
                CloseHandle(hProcess);
                return -1;
            }
            free(peb);
    
            DWORD tableSize = sizeof(GDICELL) * tableCount; // size of GDI table
            GDICELL* table = (GDICELL*)malloc(tableSize); // local table copied over to our address space
    
            // read GDI table
            err = read(hProcess, gdiTable, table, tableSize, NULL);
            if (err != 0)
            {
                printf("NtReadVirtualMemory GdiTable failed\n");
                free(table);
                CloseHandle(hProcess);
                return -1;
            }
    
            for(DWORD i = 0; i < tableCount; i++)
            {
                GDICELL cell = table[i];
                if (cell.wProcessId != dwId)
                    continue;
    
                HGDIOBJ gdiHandle = (HGDIOBJ)((cell.wUpper << 16) + i);
                WORD type = cell.wType & 0x7F;
                switch(type)
                {
                    case 1:
                        printf("DC handle:0x%08X\n", gdiHandle );
                        break;
    
                    case 4:
                        printf("Region handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 5:
                        printf("Bitmap handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 8:
                        printf("Palette handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 10:
                        printf("Font handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 16:
                        printf("Brush handle:0x%08X\n", gdiHandle);
                        break;
    
                    case 48:
                        printf("Pen handle:0x%08X\n", gdiHandle);
                        break;
    
                    default:
                        printf("Unknown type handle:0x%08X\n", gdiHandle);
                        break;
                }
            }
            free(table);
        }
        CloseHandle(hProcess);
    }
    

    EnumGdi.h:

    #pragma once
    #include "stdafx.h"
    
    // defines a GDI CELL
    typedef struct
    {
        LPVOID pKernelAddress;
        USHORT wProcessId;
        USHORT wCount;
        USHORT wUpper;
        USHORT wType;
        LPVOID pUserAddress;
    } GDICELL;
    
    // defines a GDI CELL for WOW64
    typedef struct
    {
        PVOID64 pKernelAddress;
        USHORT wProcessId;
        USHORT wCount;
        USHORT wUpper;
        USHORT wType;
        PVOID64 pUserAddress;
    } GDICELL_WOW64;
    
    // NtQueryInformationProcess for pure 32 and 64-bit processes
    typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
        IN HANDLE ProcessHandle,
        ULONG ProcessInformationClass,
        OUT PVOID ProcessInformation,
        IN ULONG ProcessInformationLength,
        OUT PULONG ReturnLength OPTIONAL
        );
    
    typedef NTSTATUS (NTAPI *_NtReadVirtualMemory)(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        OUT PVOID Buffer,
        IN SIZE_T Size,
        OUT PSIZE_T NumberOfBytesRead);
    
    // NtQueryInformationProcess for 32-bit process on WOW64
    typedef NTSTATUS (NTAPI *_NtWow64ReadVirtualMemory64)(
        IN HANDLE ProcessHandle,
        IN PVOID64 BaseAddress,
        OUT PVOID Buffer,
        IN ULONG64 Size,
        OUT PULONG64 NumberOfBytesRead);
    
    // PROCESS_BASIC_INFORMATION for pure 32 and 64-bit processes
    typedef struct _PROCESS_BASIC_INFORMATION {
        PVOID Reserved1;
        PVOID PebBaseAddress;
        PVOID Reserved2[2];
        ULONG_PTR UniqueProcessId;
        PVOID Reserved3;
    } PROCESS_BASIC_INFORMATION;
    
    // PROCESS_BASIC_INFORMATION for 32-bit process on WOW64
    // The definition is quite funky, as we just lazily doubled sizes to match offsets...
    typedef struct _PROCESS_BASIC_INFORMATION_WOW64 {
        PVOID Reserved1[2];
        PVOID64 PebBaseAddress;
        PVOID Reserved2[4];
        ULONG_PTR UniqueProcessId[2];
        PVOID Reserved3[2];
    } PROCESS_BASIC_INFORMATION_WOW64;