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

How to obtain file handle to the current executable without introducing a filesystem race?

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

这很简单,只需调用 QueryFullProcessImageName,然后使用它 return 编辑的路径打开文件并从中读取。
但是,这种方式在检索文件路径 C:\my_program.exe 和打开名为 C:\my_program.exe 的文件之间引入了 window。在此期间 window 原始文件可以替换为我不想读取的其他文件,即发生文件系统竞争。
我有一个外部强加的要求,即这场比赛不应该发生。

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

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

有没有办法使用 WinAPI 或至少 NT API 获得此句柄?
GetModuleHandleEx 好像return 完全不同的句柄。)

警告 - none 官方支持。使用了未记录的函数!

存在基于 NtAreMappedFilesTheSame

的 100% 干净的解决方案
NTSYSAPI
NTSTATUS
NTAPI
NtAreMappedFilesTheSame (
    __in PVOID File1MappedAsAnImage,
    __in PVOID File2MappedAsFile
    );

所以一般来说我们接下来需要做的事情

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

这里是完整的工作示例

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);
    }
}

你也可以找这个topic