从 Windows 10 上的 64 位进程获取 32 位进程上的 GetModuleFileNameEx [英] GetModuleFileNameEx on 32bit process from 64bit process on windows 10

查看:47
本文介绍了从 Windows 10 上的 64 位进程获取 32 位进程上的 GetModuleFileNameEx的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用以下代码从 64 位应用程序枚举 32 位进程模块名称:

I'm trying to enumerate 32bit process modules names from 64bit application using the following code:

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 中按预期工作,结果的一部分是:

The code works as expected in Windows 7, as part of the results are:

...

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

...

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

In Windows 10 the above code returns the full path but with System32 instead of SysWOW64. e.g,

...

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

...

深入寻找原因,我注意到 GetModuleFileNameEx 读取远程进程 PEB 和 LDR_TABLE_ENTRY,并且从 Windows 10 开始,LDR_TABLE_ENTRY 包含 System32 而不是 SysWOW64 的完整路径 - 也适用于 32 位应用程序.

Looking deeper for the cause, I notice that GetModuleFileNameEx reads the remote process PEB and LDR_TABLE_ENTRY, and starting from Windows 10 the LDR_TABLE_ENTRY contains the full path with System32 and not SysWOW64 - also for 32bit applications.

我也尝试使用 GetMappedFileName,但将路径从 dos 路径 (\device\harddiskvolume) 转换为标准 (c:\) 路径并不直接和高效.

I also tried to use GetMappedFileName but it isn't straight forward and efficient to translate the path from dos path (\device\harddiskvolume) to standard (c:\) path.

我想知道是否有其他简单的方法可以提取完整的 syswow64 路径.

I wonder if there are any other easy way to extract the full syswow64 path.

推荐答案

for get valid win32 file path from file nt-path - 最简单的方法 - 添加 L"\\\\?\\globalroot" (\\?\globalroot) 前缀.这是因为 CreateFileW\??\ 目录中查看并且 globalroot 是符号\??\ 中的链接,让 as 跳转到 nt 命名空间的根目录.

for get valid win32 file path from file nt-path - simplest way - add L"\\\\?\\globalroot" (\\?\globalroot) prefix. this is because CreateFileW looked from \??\ directory and globalroot is symbolic link in \??\ which let as to jump to root of nt namespace.

例如 - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll 不是绝对路径.并且 \\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dllCreateFileW 的有效 win32 路径 - 这个 api 转换得很好已知前缀 \\?\ 到 nt 前缀 \??\ 和传递名称 \??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll 到内核.解析此名称时 - 在处理符号链接 globalroot 后指向命名空间的根目录 - 我们再次得到 \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - 正确的 nt 路径.

for example - \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is nt absolute path. and \\?\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll is valid win32 path for CreateFileW - this api convert well known prefix \\?\ to nt prefix \??\ and pass name \??\globalroot\Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll to kernel. when parsing this name - after process symbolic link globalroot which point to root of namespace - we again got \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll - correct nt path.

所以如果我们需要在 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 中:

so if we need valid win32 path for use in CreateFileW - simply append this prefix to nt path. however some shell32 api not accept this form path. also it not nice looked in UI. if we want got DOS drive letter form path (this is subset of valid win32 paths) - we can use IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH which convert device name to drive letter. this ioctl take as input MOUNTDEV_NAME (declared in mountmgr.h) and output buffer is MOUNTMGR_VOLUME_PATHS. in MOUNTDEV_NAME buffer must be exactly device name, without file path. so we need break returned nt path to 2 components. for example in \Device\HarddiskVolume9\Windows\SysWOW64\ntdll.dll :

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

这里的正确方法是首先打开文件并使用 FileNameInfo 调用 GetFileInformationByHandleEx - 我们在输出中得到了文件系统路径.有了这个,我们可以使用 wcstr 作为单独的设备路径.同样,如果我们打开文件句柄 - 我们可以在调用 GetFinalPathNameByHandleWVOLUME_NAME_DOS 时使用它.这个 api 完全按照我们将要做的 - 查询文件路径,分离设备路径并调用 IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH.+ 打开/关闭安装管理器.

correct way here first open file and call GetFileInformationByHandleEx with FileNameInfo - we got file system path in output. with this we can use wcsstr for separate device path. also if we open file handle - we can use it in call GetFinalPathNameByHandleW with VOLUME_NAME_DOS. this api do exactly which we will be do - query file path, separate device path and call IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH. + open/close mount manager.

但通常的 nt 文件路径从 \Device\HarddiskVolumeX 开始.这允许先尝试快速方法 - 避免打开文件并查询它的路径.

but usual nt file path begin from \Device\HarddiskVolumeX. this allow first try fast way - avoid open file and query it path.

所以首先我们需要打开挂载管理器:

so first we need open mount manager:

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

然后我们可以运行下一个代码:

then we can run next code:

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

这篇关于从 Windows 10 上的 64 位进程获取 32 位进程上的 GetModuleFileNameEx的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆