如何在不引入文件系统竞争的情况下获取到当前可执行文件的文件句柄?

3

我需要从当前可执行文件中读取一些数据(即调试信息)。

通过调用QueryFullProcessImageName,然后使用其返回的路径打开文件并从中读取数据是很直接的。
然而,在检索文件路径C:\my_program.exe和打开名为C:\my_program.exe的文件之间会引入一个窗口。在此期间,原始文件可以被替换为我不想读取的其他文件,即发生了文件系统竞争。
我有一个外部强制要求,即这种竞争不应该发生。

基本上,我需要像不存在的QueryFullProcessImageHandle这样的东西,而不是QueryFullProcessImageName,这样我就可以在不按名称打开文件的情况下从中读取数据。

从阅读ReactOS源代码中我了解到,在Windows上这样的句柄很可能也存在,并且保存在EPROCESS结构中(作为SectionObject的一部分),实际上用于实现QueryFullProcessImageName

是否有任何方法可以使用WinAPI或至少NT API获取此句柄?
GetModuleHandleEx似乎返回完全不同的句柄。)


1
@Jean-FrançoisFabre - 这不是真的。文件在运行时可以被重命名。 - RbMm
如果确定相反的情况,那就是@RbMm。好吧,至少我今天学到了一些东西。 - Jean-François Fabre
1
我已经尝试过(刚刚在Windows 8.1上确认),它们可以被重命名并且可以创建具有相同名称的文件。 - user2665887
您能不能不要调用QueryFullProcessImageName,然后使用FILE_SHARE_READ调用CreateFile来锁定该文件,以便它无法移动、重命名、删除或写入,然后再次调用QueryFullProcessImageName以确保它仍返回您锁定的路径?只要QueryFullProcessImageName如果文件路径发生更改,则提供更新的结果,如果您在其上锁定该路径时调用它,则可以放心,因为您知道当前路径下的文件已被锁定。 (如果您非常担心,甚至可以使用GetMappedFileName来确保CreateFile句柄未移动。)还是我错误地想了这个问题? - tomysshadow
在我看来,这是有道理的,因为如果在CreateFile之前原始路径上的文件被交换了,那么第二次调用QueryFullProcessImageName中的路径就不会与第一次匹配(因为它将反映原始文件被移动到的位置),并且它不能在调用CreateFile之后交换。但由于这只是我想到的一些事情,所以我不敢说我100%相信它。 - tomysshadow
显示剩余8条评论
1个回答

1

警告 - 这些都不是官方支持的。使用了未记录的函数!

基于NtAreMappedFilesTheSame存在100%干净的解决方案。

NTSYSAPI
NTSTATUS
NTAPI
NtAreMappedFilesTheSame (
    __in PVOID File1MappedAsAnImage,
    __in PVOID File2MappedAsFile
    );

总的来说,我们需要做以下几件事情:

  1. 获取exe/dll文件的File1MappedAsAnImage地址
  2. 使用ZwQueryVirtualMemory(,MemoryMappedFilenameInformation,)获取文件名(以本地格式显示)。注意:MemoryMappedFilenameInformation总是返回调用时的当前文件名,所以如果文件已经重命名,我们将得到它的新名称。
  3. 按给定的名称打开文件
  4. 映射文件并获得File2MappedAsFile
  5. 调用NtAreMappedFilesTheSame(File1MappedAsAnImage, File2MappedAsFile)
  6. 如果我们得到STATUS_SUCCESS,则打开正确的文件 - 完成
  7. 如果我们得到STATUS_NOT_SAME_DEVICE,则需要取消映射File2MappedAsFile并返回第2步
  8. 如果我们得到其他状态,则发生了一些错误

这里是完整的工作示例:

NTSTATUS MapModule(void* File1MappedAsAnImage, void** pFile2MappedAsFile)
{
    static volatile UCHAR guz;

    PVOID stack = alloca(guz);
    union {
        PVOID buf;
        PUNICODE_STRING FileName;
    };

    SIZE_T cb = 0, rcb = 256, ViewSize;

    NTSTATUS status, s = STATUS_UNSUCCESSFUL;

    BOOL bSame;

    do 
    {
        bSame = TRUE;

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

            if (0 <= (status = NtQueryVirtualMemory(NtCurrentProcess(), File1MappedAsAnImage, MemoryMappedFilenameInformation, buf, cb, &rcb)))
            {
                DbgPrint("%wZ\n", FileName);

                OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, FileName, OBJ_CASE_INSENSITIVE };

                HANDLE hFile, hSection;
                IO_STATUS_BLOCK iosb;

                if (0 <= (s = NtOpenFile(&hFile, FILE_GENERIC_READ, &oa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT)))
                {
                    s = ZwCreateSection(&hSection, SECTION_MAP_READ, 0, 0, PAGE_READONLY, SEC_COMMIT, hFile);

                    NtClose(hFile);

                    if (0 <= s)
                    {
                        *pFile2MappedAsFile = 0;
                        s = ZwMapViewOfSection(hSection, NtCurrentProcess(), pFile2MappedAsFile, 0, 0, 0, &(ViewSize = 0), ViewUnmap, 0, PAGE_READONLY);

                        NtClose(hSection);

                        if (0 <= s)
                        {
                            switch (s = NtAreMappedFilesTheSame(File1MappedAsAnImage, *pFile2MappedAsFile))
                            {
                            case STATUS_SUCCESS:
                                DbgPrint("opened original file!");
                                return STATUS_SUCCESS;
                            case STATUS_NOT_SAME_DEVICE:
                                DbgPrint("opened another file!");
                                bSame = FALSE;
                                break;
                            default:
                                DbgPrint("status = %x\n", s);

                            }

                            ZwUnmapViewOfSection(NtCurrentProcess(), *pFile2MappedAsFile);
                        }
                    }
                }
            }

        } while (status == STATUS_BUFFER_OVERFLOW);

    } while (!bSame);

    return status < 0 ? status : s;
}

void Demo()
{
    PVOID BaseAddress;
    if (0 <= MapModule(GetModuleHandle(0), &BaseAddress))
    {
        ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
    }
}

同时您也可以查找此主题


有趣!我考虑过事后检查打开的文件,但是没有找到不涉及竞争的方法。在接受答案之前,我需要更详细地测试和理解它。 - user2665887
一如既往地,当提议使用未记录的函数时,请明确说明该解决方案是基于未记录的实现细节工作的,可能不受支持的操作系统或配置集,并且可能随时停止工作。 - IInspectable
@IInspectable - 像往常一样,您是否有关于解决方案的具体技术说明?如果不正确,请提出更好或更“有文档支持”的解决方案?这个解决方案可以在Win2000及最新的10.1607上运行-超过16年了,但有时会停止工作。 - RbMm
“你是否有一些关于解决方案的具体技术笔记?” - 是的。技术笔记是:请指出,这些都不是官方支持的东西。即使您不是专业软件开发人员,也需要了解此方面的内容。 - IInspectable
@IInspectable - 没问题。我会添加这个。或者您可以自己编辑帖子。关于技术说明,我的意思是您的看法 - 这个解决方案是否存在竞争条件?在当前版本(XP - 最新的Win10)中是否正确、稳定、有效?还是没有什么可说的? - RbMm
显示剩余3条评论

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