在Windows 10中,从64位进程获取32位进程的GetModuleFileNameEx

4
我正在尝试使用以下代码从64位应用程序中枚举32位进程模块名称: ```` ````
if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
{
    for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
    {
       TCHAR szModName[MAX_PATH] = { 0 };

        if (GetModuleFileNameEx(hProcess, hMods[i], szModName,
            sizeof(szModName) / sizeof(TCHAR)))
        {
            printf("module name is: %S", szModName);
        }
    }
}

这段代码在Windows 7上按预期运行,部分结果如下:

...

C:\Windows\**SysWOW64**\ntdll.dll

...

在Windows 10中,上述代码返回完整路径,但是使用的是System32而不是SysWOW64。例如:
...

C:\Windows\**System32**\ntdll.dll

...

深入寻找原因,我注意到GetModuleFileNameEx读取远程进程PEB和LDR_TABLE_ENTRY,从Windows 10开始,LDR_TABLE_ENTRY包含带有System32而不是SysWOW64的完整路径 - 即使对于32位应用程序也是如此。
我还尝试使用GetMappedFileName,但将路径从dos路径(\ device \ harddiskvolume)转换为标准路径(c:\)不够直接和有效。
我想知道是否有其他简单的方法提取完整的syswow64路径。

哈哈,当然可以...... - AK87
1
我检查了一下 - 这确实是系统错误。当您使用 LIST_MODULES_ALL 时,系统实际上会像您设置了 LIST_MODULES_32BIT 一样工作。您需要两次调用 EnumProcessModulesEx:一次使用 LIST_MODULES_64BIT(您将获得4个64位模块 - ntdll、wow64、wow64win、wow64cpu),另一次使用 LIST_MODULES_32BIT - RbMm
1
你可以使用ZwQueryVirtualMemoryMemoryMappedFilenameInformation来获取目标进程中任何模块的规范化、NT格式路径。或者你只需要Win32格式的路径? - RbMm
1
RbMm,这只是一个例子,谢谢。我现在将检查ZwQueryVirtualMemory和MemoryMappedFilenameInformation。谢谢。 - AK87
1
因此,使用ZwQueryVirtualMemory和MemoryMappedFilenameInformation实际上与使用GetMappedFileName相同。它返回dos路径形式(\Device\HarddiskVolumeX)。我没有找到任何有效的方法将其转换为标准路径。我尝试了https://msdn.microsoft.com/en-us/library/windows/desktop/aa366789(v=vs.85).aspx,但这不是有效的方法。 - AK87
显示剩余8条评论
1个回答

3

获取有效的Win32文件路径的最简单方法是从文件nt-path中添加L"\\\\?\\globalroot" (\\?\globalroot)前缀。这是因为CreateFileW\??\目录中查找,并且globalroot\??\中的符号链接,使我们可以跳转到nt命名空间的根。

例如 - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll是nt的绝对路径。而\\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll是用于CreateFileW的有效win32路径 - 此API将众所周知的前缀\\?\转换为nt前缀\??\并将名称\??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll传递给内核。在解析此名称时 - 在处理指向命名空间根的符号链接globalroot之后 - 我们再次获得\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - 正确的nt路径。

因此,如果我们需要在CreateFileW中使用有效的win32路径 - 只需将此前缀附加到nt路径即可。但是某些shell32 API不接受此形式的路径。而且它在UI中看起来并不好看。如果我们想从路径中获取DOS驱动器字母(这是有效win32路径的子集) - 我们可以使用IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH将设备名称转换为驱动器字母。此ioctl以MOUNTDEV_NAME(在mountmgr.h中声明)作为输入,并且输出缓冲区为MOUNTMGR_VOLUME_PATHS。在MOUNTDEV_NAME缓冲区中必须恰好是设备名称,不包括文件路径。因此,我们需要将返回的nt路径分成2个组件。例如,在\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll中:

  • \Device\HarddiskVolume9 - 设备路径
  • \Windows\SysWOW64\ntdll.dll - 文件系统路径

正确的方法是首先打开文件并调用GetFileInformationByHandleEx,使用FileNameInfo - 我们获得了输出中的文件系统路径。通过这个,我们可以使用wcsstr来分离设备路径。同时,如果我们打开文件句柄 - 我们可以在调用GetFinalPathNameByHandleW时使用VOLUME_NAME_DOS。这个API正好会做我们想要的事情 - 查询文件路径,分离设备路径并调用IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH。+ 打开/关闭挂载管理器。

但通常的NT文件路径以\Device\HarddiskVolumeX开始。这样可以先尝试快速方式 - 避免打开文件并查询其路径。

因此,我们首先需要打开挂载管理器:

#include <mountmgr.h>
HANDLE hMountManager = CreateFile(MOUNTMGR_DOS_DEVICE_NAME, 
    0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

然后我们可以运行下面的代码:
void dumpModules(HANDLE hMountManager, HANDLE hProcess)
{
    ULONG cb = 0, cbNeeded = 16;

    volatile static UCHAR guz;
    PVOID stack = alloca(guz);
    HMODULE *hMods, hmod;

__continue:

    // cumulative allocate memory in stack, not need free it
    cb = RtlPointerToOffset(hMods = (HMODULE*)alloca(cbNeeded - cb), stack);

    if (EnumProcessModulesEx(hProcess, hMods, cb, &cbNeeded, LIST_MODULES_32BIT))
    {
        if (cb < cbNeeded)
        {
            goto __continue;
        }

        if (cbNeeded /= sizeof(HMODULE))
        {
            //i use hard coded size buffers, for reduce code and show main idea
#define FILE_NAME_INFO_buffer_size  FIELD_OFFSET(FILE_NAME_INFO, FileName[MAX_PATH])
#define MOUNTDEV_NAME_buffer_size  FIELD_OFFSET(MOUNTDEV_NAME, Name[MAX_PATH])
#define MOUNTMGR_VOLUME_PATHS_buffer_size  FIELD_OFFSET(MOUNTMGR_VOLUME_PATHS, MultiSz[64])

            // + space for 0 at the end
            PFILE_NAME_INFO pfni = (PFILE_NAME_INFO)alloca(FILE_NAME_INFO_buffer_size + sizeof(WCHAR));

            PMOUNTMGR_VOLUME_PATHS pmvp = (PMOUNTMGR_VOLUME_PATHS)alloca(MOUNTMGR_VOLUME_PATHS_buffer_size);
            PMOUNTDEV_NAME pmdn = (PMOUNTDEV_NAME)alloca(MOUNTDEV_NAME_buffer_size);

            static WCHAR globalroot[] = L"\\\\.\\globalroot";

            alloca(sizeof(globalroot));
            PWSTR win32Path = pmdn->Name - RTL_NUMBER_OF(globalroot) + 1;

            memcpy(win32Path, globalroot, sizeof(globalroot));
            USHORT NameLength = pmdn->NameLength;

            do 
            {
                hmod = *hMods++;

                if (GetMappedFileNameW(hProcess, hmod, pmdn->Name, MAX_PATH))
                {
                    DbgPrint("%p %S\n",hmod, pmdn->Name);

                    PWSTR c = 0;

                    static const WCHAR HarddiskVolume[] = L"\\Device\\HarddiskVolume";

                    // fast way
                    if (!memcmp(pmdn->Name, HarddiskVolume, sizeof(HarddiskVolume) - sizeof(WCHAR)))
                    {
                        c = wcschr(pmdn->Name + RTL_NUMBER_OF(HarddiskVolume) - 1, '\\');
                    }
                    // else - for demo
                    {
                        pmdn->NameLength = NameLength;

                        HANDLE hFile = CreateFile(win32Path, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

                        if (hFile != INVALID_HANDLE_VALUE)
                        {
                            //++ just for demo
                            WCHAR DosPath[MAX_PATH];
                            if (GetFinalPathNameByHandleW(hFile, DosPath, RTL_NUMBER_OF(DosPath), VOLUME_NAME_DOS))
                            {
                                DbgPrint("%S\n", DosPath);
                            }
                            RtlGetLastNtStatus();
                            //-- just for demo

                            BOOL fOk = GetFileInformationByHandleEx(hFile, FileNameInfo, pfni, FILE_NAME_INFO_buffer_size);

                            CloseHandle(hFile);

                            if (fOk)
                            {
                                // FileName not 0 terminated
                                pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] = 0;

                                c = wcsstr(pmdn->Name, pfni->FileName);
                            }
                        }

                    }

                    if (c)
                    {
                        pmdn->NameLength = (USHORT)RtlPointerToOffset(pmdn->Name, c);

                        if (DeviceIoControl(hMountManager, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
                            pmdn, MOUNTDEV_NAME_buffer_size, 
                            pmvp, MOUNTMGR_VOLUME_PATHS_buffer_size, &cb, NULL))
                        {
                            DbgPrint("%S%S\n", pmvp->MultiSz, c);
                        }
                    }
                }

            } while (--cbNeeded);
        }
    }
}

并为记事本提供演示输出:

0000000000170000 \Device\HarddiskVolume9\Windows\SysWOW64\notepad.exe
\\?\C:\Windows\SysWOW64\notepad.exe
C:\Windows\SysWOW64\notepad.exe
0000000077A90000 \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll
\\?\C:\Windows\SysWOW64\ntdll.dll
0000000075460000 \Device\HarddiskVolume9\Windows\SysWOW64\kernel32.dll
\\?\C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\kernel32.dll
0000000074A30000 \Device\HarddiskVolume9\Windows\SysWOW64\KernelBase.dll
\\?\C:\Windows\SysWOW64\KernelBase.dll
C:\Windows\SysWOW64\KernelBase.dll
00000000749B0000 \Device\HarddiskVolume9\Windows\SysWOW64\advapi32.dll
\\?\C:\Windows\SysWOW64\advapi32.dll

你一直发布带有神秘变量的代码,这些变量要么未定义,要么未初始化。guz是什么? - Jonathan Potter
@JonathanPotter - 别复制它的定义。volatile static UCHAR guz; 我用它来获取堆栈指针。如果使用优化编译器写 stack = alloca(0),编译器会删除这个指令。但是当我使用 alloca(guz) 时,这个方法可以正常工作,因为我将 guz 声明为 volatile。当然我们也可以使用 alloca(1)。但是当 guz==0 时,我更喜欢使用 alloca(guz) - RbMm
1
这应该只需要使用 Tool Help TH32CS_SNAPMODULE32 快照即可正常工作。它调用了 RtlQueryProcessModuleInformation,该函数使用诸如 RtlGetNtSystemRootRtlReplaceSystemDirectoryInPath 等函数来重写路径,以使用 SysWOW64 代替 System32。 - Eryk Sun
1
实际上,这看起来像是WinAPI的GetModuleFileNameEx违反了良好的设计原则。RtlQueryProcessModuleInformation通过LdrQueryProcessModuleInformationEx正确委派任务给加载器,后者通过调用LdrQueryModuleInfoFromLdrEntry32LdrpGetModuleName来完成。另一方面,GetModuleFileNameEx认为它太好了,不遵循规则,完全纠缠了层次结构。它只是冲进来带着不再有效的假设。 - Eryk Sun
@eryksun 是的,你说得对。我知道这一切。然而,在不同的操作系统版本中,RtlQueryProcessModuleInformation是不同的。我记得过去它会在目标进程中创建一个远程工作线程来使用LdrQueryProcessModuleInformation从这里收集模块(并使用事件对进行同步)。但是我没有使用toolhelp api,而是您自己在目标进程中创建了一个远程线程(用于wow64带有某些技巧的情况下)。LdrpGetModuleName - RbMm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接