Search code examples
cwindowswinapint

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


I need to read some data from the current executable file (namely, debug info).

This is straightforward to do by calling QueryFullProcessImageName, then using the path returned by it to open the file and read from it.
However, this way introduces a window between retrieving the file path C:\my_program.exe and opening the file named C:\my_program.exe. During that window the original file can be replaced with some other file that I don't want to read, i.e. a filesystem race takes place.
I have an externally imposed requirement that this race should not happen.

Basically, I need something like non-existent QueryFullProcessImageHandle instead of QueryFullProcessImageName so I could read from it without opening the file by name.

From reading ReactOS sources I learned that such handle most probably exists on Windows as well and is kept in EPROCESS structure (as a part of SectionObject) and it's actually used to implement QueryFullProcessImageName.

Is there any way to obtain this handle using WinAPI or at least NT API?
(GetModuleHandleEx seems to return completely different handle.)


Solution

  • warning - none of this is officially supported. undocumted functions used!

    exist 100% clean solution based on NtAreMappedFilesTheSame

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

    so in general words we need do next

    1. got File1MappedAsAnImage address for exe/dll
    2. with ZwQueryVirtualMemory(,MemoryMappedFilenameInformation,) get FileName (in native format). note: MemoryMappedFilenameInformation always return current file name at time, when called - so if file already renamed - we got it new name
    3. open file by given name
    4. map file and got File2MappedAsFile
    5. call NtAreMappedFilesTheSame(File1MappedAsAnImage, File2MappedAsFile)
    6. if we got STATUS_SUCCESS we open correct file - done here
    7. if we got STATUS_NOT_SAME_DEVICE need unmap File2MappedAsFile and goto 2
    8. if we got other status - some error occurred

    here complete working example

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

    also you can look for this topic