从32位进程获取64位进程的命令行字符串

12
以下代码可以很好地为我获取32位进程的命令行字符串,从32位应用程序获取64位进程,从64位应用程序获取32位进程。如果我尝试从32位应用程序使用64位进程,则会出现错误,原因是PROCESS_BASIC_INFORMATION和地址大小中的结构体大小差异。所以这是我的问题 -
1)在process hacker中提供的建议(http://processhacker.sourceforge.net/forums/viewtopic.php?f=15&t=181)使用wow64函数似乎无法工作,并显示以下错误 -
NtWow64ReadVirtualMemory64 error: 8000000D while reading ProcessParameters address from A68291A0004028E0
有人尝试过并成功获取信息吗?我在他们的论坛上发布了同样的问题,询问他们的意见。
2)是否有其他方法来查询peb信息,可以可靠地适用于x86和x64?
int get_cmdline_from_pid( DWORD dwPid, char** cmdLine )
{
    DWORD dw, read;
    HANDLE hProcess;
    NtQueryInformationProcess* pNtQip;
    PROCESS_BASIC_INFORMATION pbInfo;
    UNICODE_STRING cmdline;
    WCHAR* wcmdLine;

    *cmdLine = NULL;

    hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid );
    if( !hProcess )
        return FALSE;

    pNtQip = (NtQueryInformationProcess*) GetProcAddress(GetModuleHandle("ntdll.dll"), 
                                                    "NtQueryInformationProcess");
    if(!pNtQip)
        return FALSE;

    pNtQip(hProcess, PROCESSBASICINFOMATION, &pbInfo, sizeof(pbInfo), NULL);

    #ifdef _WIN64
        ReadProcessMemory(hProcess, pbInfo.PebBaseAddress + 0x20, &dw, sizeof(dw), 
                         &read); 
    #else
        ReadProcessMemory(hProcess, pbInfo.PebBaseAddress + 0x10, &dw, sizeof(dw), 
                          &read); 
    #endif

    #ifdef _WIN64
        ReadProcessMemory(hProcess, (PCHAR)dw+112, &cmdline, sizeof(cmdline), &read);
    #else
        ReadProcessMemory(hProcess, (PCHAR)dw+64, &cmdline, sizeof(cmdline), &read);
    #endif

     wcmdLine = (WCHAR *)malloc(sizeof(char)*(cmdline.Length + 2));
     if( !wcmdLine )
         return FALSE;

     ReadProcessMemory(hProcess, (PVOID)cmdline.Buffer, wcmdLine, 
                  cmdline.Length+2, &read);

     *cmdLine = mmwin32_util_widetoansi(wcmdLine);
     free(wcmdLine);

     CloseHandle(hProcess);

     return TRUE;
}

地址A68291A0004028E0看起来非常无效。 - Elmue
2个回答

20

也许有点晚回答,但这里有一份能够实现此功能的代码。它支持32位或64位进程,并且支持WOW64上的32位进程(意味着你可以为Win32和X64编译)。它使用了未公开的函数,因此使用时要自行承担风险:-)

GetCmdLine.cpp:

#include "stdafx.h"
#include "GetCmdLine.h"

int _tmain(int argc, _TCHAR* argv[])
{
    if (argc < 2)
    {
        printf("Format is GetCmdLine <process id>\n");
        return 0;
    }

    // get process identifier
    DWORD dwId = _wtoi(argv[1]);

    // open the process
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwId);
    DWORD err = 0;
    if (hProcess == NULL)
    {
        printf("OpenProcess %u failed\n", dwId);
        err = GetLastError();
        return -1;
    }

    // determine if 64 or 32-bit processor
    SYSTEM_INFO si;
    GetNativeSystemInfo(&si);

    // determine if this process is running on WOW64
    BOOL wow;
    IsWow64Process(GetCurrentProcess(), &wow);

    // use WinDbg "dt ntdll!_PEB" command and search for ProcessParameters offset to find the truth out
    DWORD ProcessParametersOffset = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 0x20 : 0x10;
    DWORD CommandLineOffset = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? 0x70 : 0x40;

    // read basic info to get ProcessParameters address, we only need the beginning of PEB
    DWORD pebSize = ProcessParametersOffset + 8;
    PBYTE peb = (PBYTE)malloc(pebSize);
    ZeroMemory(peb, pebSize);

    // read basic info to get CommandLine address, we only need the beginning of ProcessParameters
    DWORD ppSize = CommandLineOffset + 16;
    PBYTE pp = (PBYTE)malloc(ppSize);
    ZeroMemory(pp, ppSize);

    PWSTR cmdLine;

    if (wow)
    {
        // we're running as a 32-bit process in a 64-bit OS
        PROCESS_BASIC_INFORMATION_WOW64 pbi;
        ZeroMemory(&pbi, sizeof(pbi));

        // get process information from 64-bit world
        _NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtWow64QueryInformationProcess64");
        err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
        if (err != 0)
        {
            printf("NtWow64QueryInformationProcess64 failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read PEB from 64-bit address space
        _NtWow64ReadVirtualMemory64 read = (_NtWow64ReadVirtualMemory64)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtWow64ReadVirtualMemory64");
        err = read(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL);
        if (err != 0)
        {
            printf("NtWow64ReadVirtualMemory64 PEB failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read ProcessParameters from 64-bit address space
        // PBYTE* parameters = (PBYTE*)*(LPVOID*)(peb + ProcessParametersOffset); // address in remote process address space
        PVOID64 parameters = (PVOID64) * ((PVOID64*)(peb + ProcessParametersOffset)); // corrected 64-bit address, see comments
        err = read(hProcess, parameters, pp, ppSize, NULL);
        if (err != 0)
        {
            printf("NtWow64ReadVirtualMemory64 Parameters failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read CommandLine
        UNICODE_STRING_WOW64* pCommandLine = (UNICODE_STRING_WOW64*)(pp + CommandLineOffset);
        cmdLine = (PWSTR)malloc(pCommandLine->MaximumLength);
        err = read(hProcess, pCommandLine->Buffer, cmdLine, pCommandLine->MaximumLength, NULL);
        if (err != 0)
        {
            printf("NtWow64ReadVirtualMemory64 Parameters failed\n");
            CloseHandle(hProcess);
            return -1;
        }
    }
    else
    {
        // we're running as a 32-bit process in a 32-bit OS, or as a 64-bit process in a 64-bit OS
        PROCESS_BASIC_INFORMATION pbi;
        ZeroMemory(&pbi, sizeof(pbi));

        // get process information
        _NtQueryInformationProcess query = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
        err = query(hProcess, 0, &pbi, sizeof(pbi), NULL);
        if (err != 0)
        {
            printf("NtQueryInformationProcess failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read PEB
        if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, peb, pebSize, NULL))
        {
            printf("ReadProcessMemory PEB failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read ProcessParameters
        PBYTE* parameters = (PBYTE*)*(LPVOID*)(peb + ProcessParametersOffset); // address in remote process adress space
        if (!ReadProcessMemory(hProcess, parameters, pp, ppSize, NULL))
        {
            printf("ReadProcessMemory Parameters failed\n");
            CloseHandle(hProcess);
            return -1;
        }

        // read CommandLine
        UNICODE_STRING* pCommandLine = (UNICODE_STRING*)(pp + CommandLineOffset);
        cmdLine = (PWSTR)malloc(pCommandLine->MaximumLength);
        if (!ReadProcessMemory(hProcess, pCommandLine->Buffer, cmdLine, pCommandLine->MaximumLength, NULL))
        {
            printf("ReadProcessMemory Parameters failed\n");
            CloseHandle(hProcess);
            return -1;
        }
    }
    printf("%S\n", cmdLine);
    return 0;
}

GetCmdLine.h:

#pragma once
#include "stdafx.h"

// NtQueryInformationProcess for pure 32 and 64-bit processes
typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
    IN HANDLE ProcessHandle,
    ULONG ProcessInformationClass,
    OUT PVOID ProcessInformation,
    IN ULONG ProcessInformationLength,
    OUT PULONG ReturnLength OPTIONAL
    );

typedef NTSTATUS (NTAPI *_NtReadVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN PVOID BaseAddress,
    OUT PVOID Buffer,
    IN SIZE_T Size,
    OUT PSIZE_T NumberOfBytesRead);

// NtQueryInformationProcess for 32-bit process on WOW64
typedef NTSTATUS (NTAPI *_NtWow64ReadVirtualMemory64)(
    IN HANDLE ProcessHandle,
    IN PVOID64 BaseAddress,
    OUT PVOID Buffer,
    IN ULONG64 Size,
    OUT PULONG64 NumberOfBytesRead);

// PROCESS_BASIC_INFORMATION for pure 32 and 64-bit processes
typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PVOID PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

// PROCESS_BASIC_INFORMATION for 32-bit process on WOW64
// The definition is quite funky, as we just lazily doubled sizes to match offsets...
typedef struct _PROCESS_BASIC_INFORMATION_WOW64 {
    PVOID Reserved1[2];
    PVOID64 PebBaseAddress;
    PVOID Reserved2[4];
    ULONG_PTR UniqueProcessId[2];
    PVOID Reserved3[2];
} PROCESS_BASIC_INFORMATION_WOW64;

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING;

typedef struct _UNICODE_STRING_WOW64 {
  USHORT Length;
  USHORT MaximumLength;
  PVOID64 Buffer;
} UNICODE_STRING_WOW64;

我的意思是,你的代码似乎在32位和64位上使用相同的函数和结构体,而在64位上的32位则使用其他函数和结构体。不管怎样,你的代码确实可以工作。虽然我正在使用Windows的MEMORY_BASIC_INFORMATION64。谢谢。;) - weeyoung
@weeyoung - 是的,没错。因为在编译成32位或64位时,IntPtr会自动变化大小(4或8)。 - Simon Mourier
3
还有一个 bug。当进程是 32 位时,从 64 位进程读取 PEB 地址,您将其存储在 PVOID64 中 - 也许您期望在此变量中存储 64 位,但这并不正确。MSDN 表示该指针将被截断,然后您使用截断的指针作为 64 位进程地址空间中的地址。有趣的是,截断的指针有时是有效的(如果原始指针的高序字节为 0),有时则无效。例如,在获取 IE 11 x64 的命令行时,您的代码在 Win8 上总是失败。解决方案很简单- 在所有地方使用 ULONG64 而不是 PVOID64。 - Ezh
2
感谢@Ezh指出了截断指针问题。基于此,我只是更改了以下行:PBYTE* parameters = (PBYTE*)(LPVOID)(peb + ProcessParametersOffset);到这一行:PVOID64 parameters = (PVOID64) * ((PVOID64*)(peb + ProcessParametersOffset));这个更改仅在wow块中。这解决了问题。 - Gautam Jain
1
@ceztko - 是的,这段代码应该在每个32/64位进程/操作系统组合中都能正常工作...嗯...除了32位操作系统上的64位进程 :-) - Simon Mourier
显示剩余14条评论

2
你的32位指针不足以存储目标进程64位地址空间中的地址,因此将被截断。因此,你正在尝试的是不可能的。这是Raymond Chen建议你停止使用模拟器的情况之一。
提到Raymond Chen后,我快速搜索了一下看他是否有什么有用的信息。这次搜索找到了这篇文章:为什么没有受支持的方法可以获取另一个进程的命令行? 有用的信息是观察到Win32_Process.CommandLine给出了你需要的内容(不知道怎么做)。所以,我的建议是尝试使用WMI。

谢谢您的回复。我已经在我的描述中提到了这一点。我现在想知道为什么wow函数不像在process hacker中建议的那样工作。如果有其他解决此问题的方法,请添加您的评论。 - Kartlee
2
每个提到 Raymond Chen 的答案都应该被点赞。当我看到问题标题时,他关于获取命令行的文章也是我想到的第一件事 :-) - Joey
@Joey,我甚至把他的名字写了两遍,以确保任何愿意自动升级的 Raymond 粉丝如果在扫描的话不会错过它!!;-) - David Heffernan

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