如何为 GetModuleFileNameEx 禁用 WOW64 文件系统重定向?

How to disable WOW64 file system redirection for GetModuleFileNameEx?

我是 运行 以下来自 64 位 Windows 10 上的 32 位进程:

#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

我希望收到 c:\windows\syswow64\KERNEL32.DLL 的路径,但它给了我:

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

知道为什么吗?

当我们通过 LoadLibrary[Ex]LdrLoadDll 加载 dll 时 - 首先对传输的 dll 名称进行一些预处理(比如将 api-* 转换为实际的 dll 名称或根据清单重定向 dll 名称 -众所周知的示例 comctl32.dll),然后使用此(可能已修改)dll 名称将文件加载为 dll。但是 wow fs 重定向 - 在此阶段未进行预处理。如果 dll 已成功加载 - 系统分配 LDR_DATA_TABLE_ENTRY 结构并在此处保存传输的(预处理后)dll 名称。

GetModuleFileNameEx 简单遍历 LDR_DATA_TABLE_ENTRY 双链表和搜索条目 DllBase == hModule - 如果找到 - 复制 FullDllNamelpFilename (如果缓冲区足够大)。所以它只是 return 在加载 dll 期间使用的 dll 路径。 Wow64DisableWow64FsRedirection 对此调用没有任何影响。

如果我们想要获得 dll 的真实(规范)完整路径 - 需要使用 GetMappedFileNameWZwQueryVirtualMemory with MemoryMappedFilenameInformation

所以代码可以(如果我们希望MAX_PATH就够了)

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

或者如果使用 ntapi 并正确处理任何路径长度:

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

关于问题 "way to convert it to the win32 form" - 有一个反问题 - 是为了什么? 首先,我们可以像 NtOpenFile 一样使用它(有据可查的 api),其次 - 转换为 win32 形式的最简单方法,被 CreateFileW 接受 - 添加 \?\globalroot 前缀到 nt 路径。但并非所有 win32 api(主要 shell api)都接受这种形式。如果我们想要精确的 dos 设备形式路径(又名 X:),则需要使用 IOCTL_MOUNTMGR_QUERY_POINTS - got the array of MOUNTMGR_MOUNT_POINT inside MOUNTMGR_MOUNT_POINTS 结构并搜索 DeviceName,它是我们 nt 路径的前缀,而 SymbolicLinkName 具有驱动程序字母形式。代码可以是 ~

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

我们也可以将代码更改为 MOUNTMGR_IS_VOLUME_NAME(&SymbolicLinkName) 以获取卷(持久)名称形式而不是驱动程序字母形式