cwinapiwow64

How to disable WOW64 file system redirection for GetModuleFileNameEx?


I'm running the following from a 32-bit process on a 64-bit Windows 10:

#ifndef _DEBUG
    WCHAR buffPath[MAX_PATH] = {0};
    FARPROC pfn = (FARPROC)::GetModuleHandleEx;
    HMODULE hMod = NULL;
    ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 
        GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
        (LPCTSTR)pfn, &hMod);

    PVOID pOldVal = NULL;
    if(::Wow64DisableWow64FsRedirection(&pOldVal))
    {
        ::GetModuleFileNameEx(::GetCurrentProcess(), hMod, buffPath, _countof(buffPath));
        ::Wow64RevertWow64FsRedirection(pOldVal);

        wprintf(L"Path=%s\n", buffPath);
    }
#else
#error run_in_release_mode
#endif

and I'm expecting to receive the path as c:\windows\syswow64\KERNEL32.DLL, but it gives me:

Path=C:\Windows\System32\KERNEL32.DLL

Any idea why?


Solution

  • when we load dll via LoadLibrary[Ex] or LdrLoadDll - first some pre-process transmitted dll name (say convert api-* to actual dll name or redirect dll name based on manifest - well known example comctl32.dll) and then use this (possible modified) dll name to load file as dll. but wow fs redirection - not preprocessed at this stage. if dll was successfully loaded - system allocate LDR_DATA_TABLE_ENTRY structure and save transmitted (after pre-process) dll name here as is.

    the GetModuleFileNameEx simply walk throughout LDR_DATA_TABLE_ENTRY double linked list and search entry where DllBase == hModule - if found - copy FullDllName to lpFilename (if buffer big enough). so it simply return dll path used during load dll. the Wow64DisableWow64FsRedirection have no any effect to this call.

    if we want get real (canonical) full path to dll - need use GetMappedFileNameW or ZwQueryVirtualMemory with MemoryMappedFilenameInformation

    so code can be (if we hope that MAX_PATH is enough)

    WCHAR path[MAX_PATH];
    GetMappedFileNameW(NtCurrentProcess(), hmod, path, RTL_NUMBER_OF(path));
    

    or if use ntapi and correct handle any path length:

    NTSTATUS GetDllName(PVOID AddressInDll, PUNICODE_STRING NtImageName)
    {
        NTSTATUS status;
    
        union {
            PVOID buf;
            PUNICODE_STRING ImageName;
        };
    
        static volatile UCHAR guz;
        PVOID stack = alloca(guz);
    
        SIZE_T cb = 0, rcb = 0x200;
    
        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
    
            if (0 <= (status = ZwQueryVirtualMemory(NtCurrentProcess(), AddressInDll, 
                MemoryMappedFilenameInformation, buf, cb, &rcb)))
            {
                return RtlDuplicateUnicodeString(
                    RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, 
                    ImageName, NtImageName);
            }
    
        } while (status == STATUS_BUFFER_OVERFLOW);
    
        return status;
    }
        UNICODE_STRING NtImageName;
        if (0 <= GetDllName(hmod, &NtImageName))
        {
            RtlFreeUnicodeString(&NtImageName);
        }
    

    about question "way to convert it to the win32 form" - there is a counter question - for what ? at first we can use it as is with NtOpenFile (well documented api), at second - the simplest way convert to win32 form, accepted by CreateFileW - add \\?\globalroot prefix to nt path. but not all win32 api (primary shell api) accept this form. if we want exactly dos-device form path (aka X:) need use IOCTL_MOUNTMGR_QUERY_POINTS - got the array of MOUNTMGR_MOUNT_POINT inside MOUNTMGR_MOUNT_POINTS structure and search for DeviceName which is prefix for our nt path and SymbolicLinkName have driver letter form. code can be ~

    #include <mountmgr.h>
    
    ULONG NtToDosPath(HANDLE hMM, PUNICODE_STRING ImageName, PWSTR* ppsz)
    {
        static MOUNTMGR_MOUNT_POINT MountPoint;
    
        static volatile UCHAR guz;
    
        PVOID stack = alloca(guz);
        PMOUNTMGR_MOUNT_POINTS pmmp = 0;
        DWORD cb = 0, rcb = 0x200, BytesReturned;
    
        ULONG err = NOERROR;
    
        do 
        {
            if (cb < rcb) cb = RtlPointerToOffset(pmmp = (PMOUNTMGR_MOUNT_POINTS)alloca(rcb - cb), stack);
    
            if (DeviceIoControl(hMM, IOCTL_MOUNTMGR_QUERY_POINTS, 
                &MountPoint, sizeof(MOUNTMGR_MOUNT_POINT), 
                pmmp, cb, &BytesReturned, 0))
            {
                if (ULONG NumberOfMountPoints = pmmp->NumberOfMountPoints)
                {
                    PMOUNTMGR_MOUNT_POINT MountPoints = pmmp->MountPoints;
    
                    do 
                    {
                        UNICODE_STRING SymbolicLinkName = {
                            MountPoints->SymbolicLinkNameLength,
                            SymbolicLinkName.Length,
                            (PWSTR)RtlOffsetToPointer(pmmp, MountPoints->SymbolicLinkNameOffset)
                        };
    
                        UNICODE_STRING DeviceName = {
                            MountPoints->DeviceNameLength,
                            DeviceName.Length,
                            (PWSTR)RtlOffsetToPointer(pmmp, MountPoints->DeviceNameOffset)
                        };
    
                        PWSTR FsPath;
    
                        if (RtlPrefixUnicodeString(&DeviceName, ImageName, TRUE) && 
                            DeviceName.Length < ImageName->Length &&
                            *(FsPath = (PWSTR)RtlOffsetToPointer(ImageName->Buffer, DeviceName.Length)) == '\\' &&
                            MOUNTMGR_IS_DRIVE_LETTER(&SymbolicLinkName))
                        {
                            cb = ImageName->Length - DeviceName.Length;
    
                            if (PWSTR psz = new WCHAR[3 + cb/sizeof(WCHAR)])
                            {
                                *ppsz = psz;
    
                                psz[0] = SymbolicLinkName.Buffer[12];
                                psz[1] = ':';
                                memcpy(psz + 2, FsPath, cb + sizeof(WCHAR));
    
                                return NOERROR;
                            }
    
                            return ERROR_NO_SYSTEM_RESOURCES;
                        }
    
                    } while (MountPoints++, --NumberOfMountPoints);
                }
    
                return ERROR_NOT_FOUND;
            }
    
            rcb = pmmp->Size;
    
        } while ((err = GetLastError()) == ERROR_MORE_DATA);
    
        return err;
    }
    
    
    ULONG NtToDosPath(PWSTR lpFilename, PWSTR* ppsz)
    {
        HANDLE hMM = CreateFile(MOUNTMGR_DOS_DEVICE_NAME, FILE_GENERIC_READ, 
            FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
    
        if (hMM == INVALID_HANDLE_VALUE)
        {
            return GetLastError();
        }
    
        UNICODE_STRING us;
    
        RtlInitUnicodeString(&us, lpFilename);
    
        ULONG err = NtToDosPath(hMM, &us, ppsz);
    
        CloseHandle(hMM);
    
        return err;
    }
    
        PWSTR psz;
        if (NtToDosPath(path, &psz) == NOERROR)
        {
            DbgPrint("%S\n", psz);
            delete [] psz;
        }
    

    also we can change in code to MOUNTMGR_IS_VOLUME_NAME(&SymbolicLinkName) for get volume (persistent) name form instead driver letter form