Winapi:获取拥有特定文件句柄的进程

4

目前我有一款软件,其中包含一个文件过滤驱动程序。在安装软件时,该驱动程序会以服务的方式启动:

CreateService(serviceManager, name, displayName,
                          SERVICE_START | DELETE | SERVICE_QUERY_STATUS | SERVICE_STOP,
                              SERVICE_FILE_SYSTEM_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
                                  path, NULL, NULL, NULL, NULL, NULL);

其中路径是C:\Program Files(x86)\TSU\driver\TSUfsd.sys

我在卸载软件时遇到了问题。当软件试图删除TSUfsd.sys文件时,它会给我访问被拒绝的提示。

我查看了软件如何删除驱动程序,发现它使用DeleteService函数将其删除,并等待服务从SERVICE_STOP_PENDING状态更改为SERVICE_STOPPED状态。如果在一段时间后没有发生这种情况,它将获取服务的PID,并使用ProcessTerminate将其结束,然后尝试使用rmdir /S /Q C:\Program Files(x86)\TSU\删除该文件。

我尝试查找可能持有文件句柄的进程(使用Process Explorer),但没有找到任何进程。然后我想也许服务仍然存活,所以我键入了sc query TSUfsd,但服务也消失了。

我还尝试更改权限并授予我的用户完全权限,但仍然出现相同的错误。

因此,我的问题是:

  1. 是否有其他方法来检查哪个进程(或其他内容)可以拥有文件?

  2. 我还注意到,每当我尝试使用Cygwin(rm TSUfsd.sys)删除该文件时,它都可以毫无问题地删除。使用cmd(del /f <filename>)和Cygwin之间的删除文件有什么区别?


有一个由Mark Russinovich开发的很好用的实用工具叫做hanlde.exe。除非你想在代码中自己完成,那么你可以枚举所有进程,然后检查它们的句柄? - Mihayl
在Windows 10中,系统会保留已加载的驱动程序,并且不允许删除它,直到驱动程序被卸载。 - RbMm
1个回答

9
对于这个任务,从Windows Vista开始特殊存在FileProcessIdsUsingFileInformationFILE_INFORMATION_CLASS
因此,我们需要使用FILE_READ_ATTRIBUTES打开文件(即使有人以0共享模式打开文件也是可能的。如果我们没有访问文件(通过它的DACL)但具有父目录的读取访问权限)。并调用NtQueryInformationFileFileProcessIdsUsingFileInformation。返回时,我们得到一个FILE_PROCESS_IDS_USING_FILE_INFORMATION结构(在wdm.h中定义),其中包含持有此文件(打开文件句柄或映射的部分。例如,如果此文件是exe/dll,则我们获得加载它的进程id)的ProcessId列表。还可以为每个id打印进程名称:
volatile UCHAR guz = 0;

NTSTATUS PrintProcessesUsingFile(HANDLE hFile)
{
    NTSTATUS status;
    IO_STATUS_BLOCK iosb;

    ULONG cb = 0, rcb = FIELD_OFFSET(FILE_PROCESS_IDS_USING_FILE_INFORMATION, ProcessIdList[64]);

    union {
        PVOID buf;
        PFILE_PROCESS_IDS_USING_FILE_INFORMATION ppiufi;
    };

    PVOID stack = alloca(guz);

    do 
    {
        if (cb < rcb)
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (0 <= (status = NtQueryInformationFile(hFile, &iosb, ppiufi, cb, FileProcessIdsUsingFileInformation)))
        {
            if (ppiufi->NumberOfProcessIdsInList)
            {
                PrintProcessesUsingFile(ppiufi);
            }
        }

        rcb = (ULONG)iosb.Information;

    } while (status == STATUS_INFO_LENGTH_MISMATCH);

    return status;
}

NTSTATUS PrintProcessesUsingFile(POBJECT_ATTRIBUTES poa)
{
    IO_STATUS_BLOCK iosb;
    HANDLE hFile;
    NTSTATUS status;
    if (0 <= (status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, poa, &iosb, FILE_SHARE_VALID_FLAGS, 0)))
    {
        status = PrintProcessesUsingFile(hFile);
        NtClose(hFile);
    }

    return status;
}

NTSTATUS PrintProcessesUsingFile(PCWSTR FileName)
{
    UNICODE_STRING ObjectName;
    NTSTATUS status = RtlDosPathNameToNtPathName_U_WithStatus(FileName, &ObjectName, 0, 0);
    if (0 <= status)
    {
        OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };
        status = PrintProcessesUsingFile(&oa);
        RtlFreeUnicodeString(&ObjectName);
    }

    return status;
}

NTSTATUS PrintProcessesUsingFile(PFILE_PROCESS_IDS_USING_FILE_INFORMATION ppiufi)
{
    NTSTATUS status;

    ULONG cb = 0x8000;

    do 
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        if (PVOID buf = new BYTE[cb])
        {
            if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
            {
                union {
                    PVOID pv;
                    PBYTE pb;
                    PSYSTEM_PROCESS_INFORMATION pspi;
                };

                pv = buf;
                ULONG NextEntryOffset = 0;

                do 
                {
                    pb += NextEntryOffset;

                    ULONG NumberOfProcessIdsInList = ppiufi->NumberOfProcessIdsInList;

                    PULONG_PTR ProcessIdList = ppiufi->ProcessIdList;
                    do 
                    {
                        if (*ProcessIdList++ == (ULONG_PTR)pspi->UniqueProcessId)
                        {
                            DbgPrint("%p %wZ\n", pspi->UniqueProcessId, &pspi->ImageName);
                            break;
                        }
                    } while (--NumberOfProcessIdsInList);

                } while (NextEntryOffset = pspi->NextEntryOffset);
            }

            delete [] buf;
        }

    } while (status == STATUS_INFO_LENGTH_MISMATCH);

    return status;
}

alloca(v)的目的是什么?如果我这样做,buf = alloca(rcb),NtQueryInformationFile(hFile, &iosb, ppiufi, rcb, ...),而不使用堆栈和cb。它运行良好。但我认为您可能有一些原因这样做。 - undefined
@vincent 这段代码是为了分配一个未知大小的缓冲区而设计的。如果我们在开始时知道需要的大小,我们可以直接使用alloca(rcb)。但是如果我们不知道大小,并且大小在调用NtQueryInformationFile时可能会发生变化,我使用这段非常通用的代码。cb = RtlPointerToOffset(buf, stack)表示当前分配的大小,如果我们需要更大的rcb,即cb < rcb,我们需要再分配rcb-cb的大小。如果使用/RTCs选项,在分配堆栈后,会在堆栈中设置一个用于检查的魔术值。因此,这段代码不适用于累积的alloca操作。 - undefined
@vincent - stack = alloca(guz) 是分配缓冲区的结束(需要确切地这样,而不是 alloca(0) )。 buf - 是分配缓冲区的开始 - 由 alloca 返回 在这种情况下,RtlPointerToOffset(buf, stack); 是当前分配的大小 - 我使用 cb 表示这个 rcb - 缓冲区所需的大小 如果我们已经分配了 cb < rcb - 需要更多的分配 rcb - cb。 所以我执行 buf = alloca(rcb - cb) 然后再次计算大小 cb = RtlPointerToOffset(buf, stack) - undefined
所以alloca(guz)是函数堆栈中可以分配的最高地址,当内存不足时,它将分配rcb-cb,并且buf将是这两次分配的组合。我对以下两点感到困惑:第一点是,如果我在这两次分配之间定义了一些变量,这些变量会破坏"buf"吗?第二点是,rcb-cb和cb = RtlPointerToOffset(buf, stack)之间的关系是什么?cb = RtlPointerToOffset(buf, stack)返回至少64,并且大于rcb-cb。是否有类似于至少分配64并按某个大小字节递增的页面之类的东西?@RbMm - undefined
@vincent - 如果我在这两次分配之间定义了一些变量,这些变量会破坏"buf"吗?- 当然不会。不会有任何破坏。我的代码是正确的。 - undefined
显示剩余22条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接