如何为 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
- 如果找到 - 复制 FullDllName
到 lpFilename
(如果缓冲区足够大)。所以它只是 return 在加载 dll 期间使用的 dll 路径。 Wow64DisableWow64FsRedirection
对此调用没有任何影响。
如果我们想要获得 dll 的真实(规范)完整路径 - 需要使用 GetMappedFileNameW
或 ZwQueryVirtualMemory
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)
以获取卷(持久)名称形式而不是驱动程序字母形式
我是 运行 以下来自 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
- 如果找到 - 复制 FullDllName
到 lpFilename
(如果缓冲区足够大)。所以它只是 return 在加载 dll 期间使用的 dll 路径。 Wow64DisableWow64FsRedirection
对此调用没有任何影响。
如果我们想要获得 dll 的真实(规范)完整路径 - 需要使用 GetMappedFileNameW
或 ZwQueryVirtualMemory
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)
以获取卷(持久)名称形式而不是驱动程序字母形式