Close

Find processes holding a file open

A project log for MultiBot CNC v2

A low cost 3D printed CNC that can be built with minimal tools yet is capable of great things.

david-tuckerDavid Tucker 02/13/2022 at 04:100 Comments

This is a little off topic, but I spent way too much time trying to work this out and need somewhere to post it. I wanted to get a list of other processes that where holding open a file (or other object) that I also was holding open.  This is the core of what SysInternals Handle application is doing.  

There is some limited example code out there that shows you how to do this using only the path to the file in question.  However these example projects don't work if the process holding open the handle are running at an elevated privilege level to your own application.  This is because they rely on you cloning a handle owned by the other process, something you can't do for security reasons if you don't have the right permissions.

In my case I already have a handle opened to the file of interest.  My inspiration is to search through all available handles on the computer to find my own handle and then use that to look up the unique object pointer in order to compare against other handles to see if they also are pointing to the same object.  That way we can identify the handle without needing to clone a pointer and we only need to use a much lower risk call to work out the process name.  This should work with any resource and any process running at any level of elevation.  It is also much faster since we don't have to mess with every process on the computer.

Note that this only cares about handles, it does not mater what resource you have opened.  It can be used to find who else has a file opened, but it works just as well with loaded dll's or other resources that could have a windows handle.

 
// SYSTEM_INFORMATION_CLASS from winternl.h
#define SystemExtendedHandleInformation 64	//0x40
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
 
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
    void *Object;
    ULONG_PTR UniqueProcessId;
    ULONG_PTR HandleValue;
    ULONG GrantedAccess;
    USHORT CreatorBackTraceIndex;
    USHORT ObjectTypeIndex;
    ULONG HandleAttributes;
    ULONG Reserved;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX;
 
typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
    ULONG_PTR NumberOfHandles;
    ULONG_PTR Reserved;
    SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX;
 
typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)(
		ULONG SystemInformationClass,
		PVOID SystemInformation,
		ULONG SystemInformationLength,
		PULONG ReturnLength
		);
 
_NtQuerySystemInformation NtQuerySystemInformation = 
		(_NtQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
 
// Print out every process that has the resource opened corresponding to the passed in handle
// that is get a list of applications that have a speciffic file opened.
void GetProcessNameFromHandle(const HANDLE h)
{
	// is handle valid
	if(h != 0)
	{
		NTSTATUS status = -1;
	 
		// take a wild guess at the size of the data, there are likely hundreds of thousands of handles opened
		ULONG SystemInfoLength = 5242880; // 5 megs
		SYSTEM_HANDLE_INFORMATION_EX *SystemHandleInfo = (SYSTEM_HANDLE_INFORMATION_EX*)new char[SystemInfoLength];
	 
		// try to grab the data
		// we can't use the ReturnLength parameter to work out this size, it always returns a small number that is incorrect.
		while(SystemHandleInfo && (status = NtQuerySystemInformation(SystemExtendedHandleInformation, 
								SystemHandleInfo, SystemInfoLength, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
		{
			// if our guess was too small then keep trying larger blocks
			delete [] SystemHandleInfo;
			SystemInfoLength *= 2; // double the memory over the last try
			SystemHandleInfo = (SYSTEM_HANDLE_INFORMATION_EX*)new char[SystemInfoLength];
		}
	 
		// did we grab data?
		if(SystemHandleInfo && NT_SUCCESS(status))
		{
			// work out our own process id
			ULONG CurrentProcessId = GetCurrentProcessId();
	 
			// search twice through the data, once to find our own handle to the files
			// and again to find any other processes with handles to the files
			int hIndex = -1;
			for(unsigned int i = 0; i < SystemHandleInfo->NumberOfHandles; i++)
			{
				// if our own process
				if (CurrentProcessId == SystemHandleInfo->Handles[i].UniqueProcessId)
				{
					// did we find the handle?
					if(SystemHandleInfo->Handles[i].HandleValue == (ULONG_PTR)h)
					{
						hIndex = i;
						break; // no need to keep looking
					}
				}
			}
	 
			// did we find our own handle to the file?
			if(hIndex >= 0)
			{
				// search again for other records matching our found files
				for(unsigned int i = 0; i < SystemHandleInfo->NumberOfHandles; i++)
				{
					// don't bother looking at our own process
					if (CurrentProcessId != SystemHandleInfo->Handles[i].UniqueProcessId)
					{
						// if were looking at the same object, this works because the object pointer is unique across all processes
						if(SystemHandleInfo->Handles[i].Object == SystemHandleInfo->Handles[hIndex].Object)
						{
							// then work out the name of the process that has the handle open
							// we use PROCESS_QUERY_LIMITED_INFORMATION so we can open the handle even if the process has elevated priveleges
							char processName[MAX_PATH];
							HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)SystemHandleInfo->Handles[i].UniqueProcessId);
							if(hProcess)
							{
								DWORD tMaxLen = MAX_PATH;
								if(QueryFullProcessImageNameA(hProcess, 0, processName, &tMaxLen))
								{
									printf("%s\n", processName);
								}
					 
								CloseHandle(hProcess);
							}
						}
					}
				}
			}
		}
	 
		if(SystemHandleInfo != NULL)
			delete [] SystemHandleInfo;
	}
}
 

Discussions