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

c00*_*0fd 1 c winapi ntfs wow64 windows-kernel

我在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
Run Code Online (Sandbox Code Playgroud)

而且我期待收到路径c:\windows\syswow64\KERNEL32.DLL,但它给了我:

Path=C:\Windows\System32\KERNEL32.DLL
Run Code Online (Sandbox Code Playgroud)

知道为什么吗?

RbM*_*bMm 6

当我们通过LoadLibrary[Ex]或加载dll时LdrLoadDll- 首先是一些预处理传输的dll名称(比如转换api-*为实际的dll名称或基于清单重定向dll名称 - 众所周知的例子comctl32.dll)然后使用这个(可能修改的)dll名称将文件加载为dll.但是重定向 - 在这个阶段没有预处理.如果dll成功加载 - 系统分配LDR_DATA_TABLE_ENTRY结构并保存传输(在预处理之后)dll名称.

GetModuleFileNameEx简单地走在整个LDR_DATA_TABLE_ENTRY双链表和搜索条目,其中DllBase == hModule-如果发现-复制FullDllNamelpFilename(如果缓冲区足够大).所以它只返回加载dll期间使用的dll路径.在Wow64DisableWow64FsRedirection有这一呼吁没有任何影响.

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

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

WCHAR path[MAX_PATH];
GetMappedFileNameW(NtCurrentProcess(), hmod, path, RTL_NUMBER_OF(path));
Run Code Online (Sandbox Code Playgroud)

或者如果使用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);
    }
Run Code Online (Sandbox Code Playgroud)

关于"将其转换为win32形式的方式"的问题 - 有一个反问题 - 为什么?首先我们可以使用它NtOpenFile(有文件记录的api),第二种 - 最简单的方式转换为win32形式,接受CreateFileW- 添加\\?\globalroot前缀到nt路径.但并非所有win32 api(主shell api)都接受此表单.如果我们想要完全dos-device形式路径(又名X:)需要使用IOCTL_MOUNTMGR_QUERY_POINTS- 得到MOUNTMGR_MOUNT_POINT内部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;
    }
Run Code Online (Sandbox Code Playgroud)

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