Track down GDI and User handle leaks. You can identify which application leaks GDI/User handles. In the details view, you can see all the properties of the handles. For bitmaps, icons and cursors you see and save the real image.

On 32 and 64 Bit versions of Windows 10, Windows 8.1, Windows 8, Windows 7, Vista, Server 2012, Server 2008, Server 2003, XP, 2000 and NT4, Bear displays the usage of
All GDI Objects [hDC, hRegion, hBitmap, hPalette, hFont, hBrush]
All User Objects [hWnd, hMenu, hCursor, SetWindowsHookEx, SetTimer and some other stuff]
Memory pages count and size [LoadLibrary() and LoadLibraryEx(LOAD_LIBRARY_AS_DATAFILE)]

On Windows 98 and Windows 95, Bear displays the usage of
All GDI Objects [hDC, hRegion, hBitmap, hPalette, hFont, hBrush]
GDI, System and User Resources


Handle Details

A double click on a process opens the details view. Each GDI/User object is shown with a description. Make a right click onto Bitmaps, Icons and Cursors to save them.



Changes are displayed in green and red to easily spot the differences.



Enable logging to a CSV file to trace changes over time.
If a specified handle count limit is reached, a notification can be send. Using Email, or logfile.
Version 1.60 · 982kb · VirusTotal Report
New: Cleanup code fixed History
Do you like the software? Make a Donation!
You don’t need to have a paypal account in order to make a donation.
Do you need additional features or would you like to help with the translation? Send a Mail!

Install using package managers:

winget install --id the-sz.Bear -e --forcecontent_copy
choco install bear.portable -y --ignore-checksumcontent_copy

Supported Operating Systems:

Windows 11 Windows 11
Windows 10 Windows 10
Windows 8 Windows 8
Windows 7 Windows 7
Windows Vista Windows Vista
Windows XP Windows XP
Windows 2000 Windows 2000
Windows NT 4.0 Windows NT 4.0
Windows Server Windows Server
Windows Me Windows Me
Windows 98 Windows 98
Loading comments…

Command line options:

/refresh <1000> Refresh rate in milliseconds
/log <file name> Log file name
/details <pid> Show details for given pid
/exportList <file.xml> Export details handle list and exit, use in combination with /details


I’m using several different functions to get the information:

gdi32.dll GdiQueryTable() usable on Windows NT 4 up to Windows XP
PEB->GdiSharedHandleTable usable on Windows NT 4 and higher
user32.dll gSharedInfo->aheList / HANDLEENTRY usable on Windows NT 4 and higher
user32.dll GetGuiResources() usable on Windows 2000 and higher
kernel32.dll GetProcessHandleCount() usable on Windows XP SP1 and higher
ntdll.dll NtQuerySystemInformation() usable on Windows NT 4 and higher
user32.dll QueryUserCounters() usable on Windows 2000 up to Windows XP
gdi32.dll GetObjectType() usable on Windows 95/98 only
rsrc32.dll MyGetFreeSystemResources32() usable on Windows 95/98 only
kernel32.dll VirtualQueryEx() usable on Windows NT 4 and higher

Some details about the used GDI Objects method:

The function gdi32.dll:GdiQueryTable returns a pointer to a table with all GDI Objects currently present in Windows. It can contain up to 16384 entries. On Windows NT4, member ‘wProcessIdVersion4’ contains the owner process id. On Windows 2000 and above, ‘wProcessIdVersion5’ contains the owner process id. After masking ‘wHandleType’ with 0x1F you get the handle type.

			#define GDI_TABLE_MAX_ENTRIES 		16384

			// 16 bytes large
			#pragma pack(1)
			typedef struct
				DWORD	dwHandle;				// must be >0x80000000
				WORD	wProcessIdVersion5;
				WORD	wProcessIdVersion4;
				WORD	wUnknown;
				WORD	wHandleType;			// mask with 0x1F
				DWORD	dwUnknown;
			#pragma pack()


				(wHandleType & 0x1F)			HGDIOBJ
				0x01							hDC
				0x04							hRegion
				0x05							hBitmap
				0x08							hPalette
				0x0A							hFont
				0x10							hBrush
				all other						unknown


Some details about the used User Objects method:

The function user32.dll:QueryUserCounters fills an array with the counts of all user objects. The real function name is NtUserQueryUserCounters(). This is a stub to the function _QueryUserHandles().
Please note SetTimer() and SetClipboardData() handles are always counted for pid 0 (Idle Process) because they are not owned by the process itself.

			typedef BOOL (__stdcall *QUERY_USER_COUNTERS)(IN DWORD dwQueryType,IN LPVOID pvIn,IN DWORD dwInSize,OUT LPVOID pvResult,IN DWORD dwOutSize);

			#define QUERYUSER_TYPE_USER			0x01

				dwQueryType:					QUERYUSER_TYPE_USER.
				pvIn:							pointer to an array of process id DWORD’s.
				dwInSize:						size of process id array.
				pvResult:						pointer to an array for the User Object count DWORD’s
				dwOutSize:						size of count array.

I’m using as pvIn only one process id. Therefore dwInSize is sizeof(DWORD). pvResult must point to an array which has the exact size for all possible User Object counts. Under Windows 2000 there are 18, under Windows XP 20. Therefore dwOutSize is 18*sizeof(DWORD) or 20*sizeof(DWORD).

Windows 7 - 64 Bit:

Windows XP:

Windows 98:

Download Mirrors: