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

时间:2018-01-10 00:19:45

标签: 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

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

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

知道为什么吗?

1 个答案:

答案 0 :(得分:6)

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

GetModuleFileNameEx只需遍历LDR_DATA_TABLE_ENTRY双重链接列表和搜索条目DllBase == hModule - 如果找到 - 将FullDllName复制到lpFilename(如果缓冲区足够大) )。所以它只返回加载dll期间使用的dll路径。 Wow64DisableWow64FsRedirection对此次通话没有任何影响。

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

所以代码可以(如果我们希望WCHAR path[MAX_PATH]; GetMappedFileNameW(NtCurrentProcess(), hmod, path, RTL_NUMBER_OF(path)); 足够的话)

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

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

NtOpenFile

关于“将其转换为win32表单的方式”的问题 - 有一个反问题 - 为什么? 首先,我们可以按原样使用CreateFileW(记录良好的api),第二种 - 转换为win32形式的最简单方式,由\\?\globalroot接受 - 将X:前缀添加到nt路径。但并非所有win32 api(主shell api)都接受此表单。如果我们想要完全dos-device形式路径(又名#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; } )需要使用MemoryMappedFilenameInformation - 在IOCTL_MOUNTMGR_QUERY_POINTS结构中获取MOUNTMGR_MOUNT_POINT数组并搜索DeviceName,它是我们的前缀nt path和SymbolicLinkName有驱动程序字母形式。代码可以〜

MOUNTMGR_IS_VOLUME_NAME(&SymbolicLinkName)

我们也可以将代码更改为$forecast = file_get_contents("https://www.astrospeak.com/horoscope/taurus");以获取卷(持久)名称格式而不是驱动程序字母格式