List open files on Windows?

158 views Asked by At

For a file browser I'm working on, I need to generate a list of open files on a Windows system.

I've begun with code that successfully generates a list of process IDs. Is there a way to turn this into something that lists all the files that each process has open?

#include <cstdlib>
#include <iostream>
#include <cstring>

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>

using namespace std;


void PrintProcessNameAndID(DWORD processID) {
    TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");

    // Get a handle to the process.

    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
            PROCESS_VM_READ,
            FALSE, processID);

    // Get the process name.

    if (NULL != hProcess) {
        HMODULE hMod;
        DWORD cbNeeded;

        if (EnumProcessModules(hProcess, &hMod, sizeof (hMod),
                &cbNeeded)) {
            GetModuleBaseName(hProcess, hMod, szProcessName,
                    sizeof (szProcessName) / sizeof (TCHAR));
        }
    }

    // Print the process name and identifier.

    _tprintf(TEXT("%s  (PID: %u)\n"), szProcessName, processID);

    // Release the handle to the process.

    CloseHandle(hProcess);
}

int startup(void) {
    // Get the list of process identifiers.

    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;

    if (!EnumProcesses(aProcesses, sizeof (aProcesses), &cbNeeded)) {
        return 1;
    }


    // Calculate how many process identifiers were returned.

    cProcesses = cbNeeded / sizeof (DWORD);

    // Print the name and process identifier for each process.

    for (i = 0; i < cProcesses; i++) {
        if (aProcesses[i] != 0) {
            PrintProcessNameAndID(aProcesses[i]);
        }
    }

    return 0;
}

int main(int argc, char** argv) {

    startup();

    return 0;
}

1

There are 1 answers

3
The Forest And The Trees On

The typical way to do this is to use the "undocumented" features of NtQueryInformationProcess to enumerate over the handles. Note that this will give back file-paths relative to Devices - you'd need to use GetVolumePathName to then relate this back to drive letters.

#include <windows.h>
#include <memory>
#include <string>

#define NT_SUCCESS(status) (status >= 0)

#define STATUS_INFO_LENGTH_MISMATCH      ((NTSTATUS)0xC0000004L)

enum PROCESSINFOCLASS {
    ProcessHandleInformation = 51
};

struct PROCESS_HANDLE_TABLE_ENTRY_INFO {
    HANDLE HandleValue;
    ULONG_PTR HandleCount;
    ULONG_PTR PointerCount;
    ULONG GrantedAccess;
    ULONG ObjectTypeIndex;
    ULONG HandleAttributes;
    ULONG Reserved;
};

// private
struct PROCESS_HANDLE_SNAPSHOT_INFORMATION {
    ULONG_PTR NumberOfHandles;
    ULONG_PTR Reserved;
    PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1];
};

typedef NTSTATUS(NTAPI* t_NtQueryInformationProcess)(
    HANDLE ProcessHandle,
    PROCESSINFOCLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength);

t_NtQueryInformationProcess NtQueryInformationProcess()
{
    static t_NtQueryInformationProcess f_NtQueryInformationProcess = NULL;
    if (!f_NtQueryInformationProcess)
    {
        HMODULE h_NtDll = GetModuleHandle("Ntdll.dll"); // Ntdll is loaded into EVERY process!
        f_NtQueryInformationProcess = (t_NtQueryInformationProcess)GetProcAddress(h_NtDll, "NtQueryInformationProcess");
    }
    return f_NtQueryInformationProcess;
}

struct ThreadData {
    HANDLE hDup;
    std::wstring name;
};

enum OBJECT_INFORMATION_CLASS
{
    ObjectBasicInformation,
    ObjectNameInformation,
    ObjectTypeInformation,
    ObjectAllInformation,
    ObjectDataInformation
};

struct UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
};

struct OBJECT_NAME_INFORMATION
{
    UNICODE_STRING Name; // defined in winternl.h
    WCHAR NameBuffer;
};

struct OBJECT_TYPE_INFORMATION {
    UNICODE_STRING TypeName;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
};

typedef NTSTATUS(NTAPI* t_NtQueryObject)(HANDLE Handle, OBJECT_INFORMATION_CLASS Info, PVOID Buffer, ULONG BufferSize, PULONG ReturnLength);

t_NtQueryObject NtQueryObject();
std::wstring GetFileHandleNameInformation_GetMappedFileName(HANDLE fileHandle);

/// <summary>
/// Loads/retrieves the NtQueryObject function from NtDll 
/// </summary>
/// <returns>The functor for the NtQueryObject function</returns>
t_NtQueryObject NtQueryObject()
{
    static t_NtQueryObject f_NtQueryObject = NULL;
    if (!f_NtQueryObject)
    {
        HMODULE h_NtDll = GetModuleHandle("Ntdll.dll"); // Ntdll is loaded into EVERY process!
        f_NtQueryObject = (t_NtQueryObject)GetProcAddress(h_NtDll, "NtQueryObject");
    }
    return f_NtQueryObject;
}

/// <summary>
/// Calls NtQueryObject to get the filename for the file handle
/// Remark: NtQueryObject uses Device name for the drives
/// </summary>
/// <param name="fileHandle">The file handle</param>
/// <returns>The filename.</returns>
std::wstring GetFileHandleNameInformation_NtQueryObject(HANDLE fileHandle)
{
    ULONG szNeeded;
    auto status = ::NtQueryObject()(fileHandle, ObjectNameInformation, NULL, 0, &szNeeded);

    //Get handle name information
    std::unique_ptr<BYTE[]> nameBuffer = std::make_unique<BYTE[]>(szNeeded);
    OBJECT_NAME_INFORMATION* nameInfo = (OBJECT_NAME_INFORMATION*)nameBuffer.get();
    status = NtQueryObject()(fileHandle, ObjectNameInformation, nameInfo, szNeeded, &szNeeded);

    return std::wstring{ nameInfo->Name.Buffer, nameInfo->Name.Length / sizeof(WCHAR) };
}

std::vector<std::wstring> ProcessFileHandles(HANDLE hProcess)
{
    //Get the concatenated list of handles for the process. This gets the buffer length first, then gets the actual buffer of information
    ULONG size = 1 << 10;
    std::unique_ptr<BYTE[]> buffer;
    for (;;)
    {
        buffer = std::make_unique<BYTE[]>(size);
        auto status = ::NtQueryInformationProcess()(hProcess, ProcessHandleInformation, buffer.get(), size, &size);

        if (NT_SUCCESS(status))
        {
            break;
        }

        if (status == STATUS_INFO_LENGTH_MISMATCH)
        {
            size += 1 << 10;
            continue;
        }

        printf("Error enumerating handles\n");
        return {};
    }

    bool result = false;

    //Look through the handles to find our target file path
    std::vector<std::wstring> filePaths;
    auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.get());

    for (ULONG i = 0; i < info->NumberOfHandles; i++)
    {
        HANDLE h = info->Handles[i].HandleValue;
        HANDLE hTarget;
        if (!::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget, 0, FALSE, DUPLICATE_SAME_ACCESS))
        {
            continue;   // move to next handle
        }

        //Get size of buffer
        ULONG szNeeded;
        auto status = NtQueryObject()(hTarget, ObjectTypeInformation, NULL, 0, &szNeeded);

        //Get handle type information
        std::unique_ptr<BYTE[]> typeBuffer = std::make_unique<BYTE[]>(szNeeded);
        OBJECT_TYPE_INFORMATION* typeInfo = (OBJECT_TYPE_INFORMATION*)typeBuffer.get();
        status = NtQueryObject()(hTarget, ObjectTypeInformation, typeInfo, szNeeded, &szNeeded);

        if (!NT_SUCCESS(status) || typeInfo == nullptr)
        {
            continue;
        }

        if (::_wcsnicmp(typeInfo->TypeName.Buffer, L"File", 4) == 0)
        {
            std::wstring handlePath = GetFileHandleNameInformation_NtQueryObject(hTarget);
            filePaths.push_back(handlePath);
        }

        CloseHandle(hTarget);
    }

    return filePaths;
}