1

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?

0xC0000022L
  • 20,597
  • 9
  • 86
  • 152
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 2
    the `GetModuleHandleEx` simply returned saved module name, used for load dll. the `Wow64DisableWow64FsRedirection` have not and can not any effect for this. you can use say `GetMappedFileName` for get real file system path – RbMm Jan 10 '18 at 00:29
  • @RbMm: Where was it saved in, `PEB` struct? So yeah, `GetMappedFileName` returns the correct path, sort of. It's in device-path form. Do you know of any easy way to convert it to the form used by Explorer, i.e. `C:\ ` instead of `\Device\HardiskVolumeN`? And btw, `GetMappedFileName` doesn't need `Wow64DisableWow64FsRedirection` – c00000fd Jan 10 '18 at 00:38
  • it saved in `_LDR_DATA_TABLE_ENTRY` the `GetModuleFileNameEx` return from here. even preserve case in file name/path. you can easy test this by self - load some dll and use unusual case combination in name - `GetModuleFileNameEx` - return you exactly this back. when `GetMappedFileName` - return you true file system path – RbMm Jan 10 '18 at 00:40
  • @RbMm: `_LDR_DATA_TABLE_ENTRY` that's in the kernel space, right? – c00000fd Jan 10 '18 at 00:41
  • of course `GetMappedFileName` not depend from `Wow64DisableWow64FsRedirection` too (this is shell over `ZwQueryVirtualMemory(,,MemoryMappedFilenameInformation..)` – RbMm Jan 10 '18 at 00:42
  • 1
    no, `_LDR_DATA_TABLE_ENTRY` - this is user space – RbMm Jan 10 '18 at 00:42
  • and for what you need convert nt path to win32 form ? – RbMm Jan 10 '18 at 00:43
  • @RbMm: Sorry, I was gone for a while. So yeah, like you pretty much surmised in your answer, I need it for the shell APIs. – c00000fd Jan 10 '18 at 09:59
  • for shell api unfortunately need only driver letter path, so need `IOCTL_MOUNTMGR_QUERY_POINTS` for convert path – RbMm Jan 10 '18 at 10:05
  • @RbMm: Yeah, thanks. I wish MS had a single API for those conversions. – c00000fd Jan 10 '18 at 10:07

1 Answers1

8

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

RbMm
  • 31,280
  • 3
  • 35
  • 56