如何查询正在运行的进程的参数列表?(Windows,C++)

7

针对特定的Windows进程,我想知道它是以哪些命令行参数启动的。Windows任务管理器能够显示这些信息。


根据这个评论,看起来你对问题的解决方案存在误解,而不是询问如何实现你最初想要的。如果你想停止多个进程执行相同的工作,可以使用互斥锁或信号量来“锁定”特定的工作部分。 - Deanna
4个回答

8
假设您已经知道进程ID,请使用OpenProcess获取其句柄(如文档中所述,这需要提升权限)。然后使用NtQueryInformationProcess获取详细的进程信息。使用ProcessBasicInformation选项获取进程的PEB - 这包含另一个结构指针,通过它您可以获得命令行。

看起来正是我正在寻找的。谢谢。 - beutelfuchs
1
这些API和数据结构可能会在不同的Windows版本中发生变化。为了保持应用程序的兼容性,请避免使用这些API和数据结构。 - beutelfuchs
是的,不过那份文档已经存在多年了,我个人认为那不是一个主要问题。然而,如果你更喜欢的话,也可以使用WMI获取所需信息。请参阅http://msdn.microsoft.com/en-us/library/aa394372%28v=VS.85%29.aspx上的Win32_Process类的CommandLine属性。 - Steve Townsend
我今天第一次听到“WMI”。看起来不是最直观的东西。我会研究它的。再次感谢。 - beutelfuchs
是的,它非常全面,但由于基于COM编码实践,使用起来有点过时。如果可以的话,使用.Net Framework中的System.Management命名空间编写WMI代码会更容易一些。我的答案中的C++/Win32方法可能是现在的更快解决方案 - 或许你可以等待重新实现使用WMI,如果你必须的话? - Steve Townsend
是的,我会从MSDN复制所需的结构到我的代码中,并暂时读取PEB。 - beutelfuchs

6

远程线程注入:

您可以使用远程线程注入,调用GetCommandLine(),然后将结果返回到IPC。这在Windows XP上可能大多数情况下有效,但在Windows Vista或更高版本上,它不适用于系统和服务进程。这是因为CreateRemoteThread仅在与调用者相同会话ID的进程中工作 - 在Windows Vista中,服务和其他系统进程在会话0中运行,而用户程序在更高的会话中运行。最好且安全的方式是读取每个Windows进程中存在的结构。

PEB结构:

进程环境块(PEB)通常存储在进程内存的高区域,位于0x7ff00000以上。这些区域还包含线程环境块(TEBs)。 PEB地址对于几乎每个进程都是不同的,因此您不能简单地使用硬编码的常量。

#include <windows.h>
#include <stdio.h>
#include "Winternl.h"

typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
    HANDLE ProcessHandle,
    DWORD ProcessInformationClass,
    PVOID ProcessInformation,
    DWORD ProcessInformationLength,
    PDWORD ReturnLength
    );

PVOID GetPebAddress(HANDLE ProcessHandle)
{
    _NtQueryInformationProcess NtQueryInformationProcess =
        (_NtQueryInformationProcess)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
    PROCESS_BASIC_INFORMATION pbi;

    NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL);

    return pbi.PebBaseAddress;
}

int wmain(int argc, WCHAR *argv[])
{
    int pid;
    HANDLE processHandle;
    PVOID pebAddress;
    PVOID rtlUserProcParamsAddress;
    UNICODE_STRING commandLine;
    WCHAR *commandLineContents;

    if (argc < 2)
    {
        printf("Usage: getprocesscommandline [pid]\n");
        return 1;
    }

    pid = _wtoi(argv[1]);

    if ((processHandle = OpenProcess(
        PROCESS_QUERY_INFORMATION | /* required for NtQueryInformationProcess */
        PROCESS_VM_READ, /* required for ReadProcessMemory */
        FALSE, pid)) == 0)
    {
        printf("Could not open process!\n");
        return GetLastError();
    }

    pebAddress = GetPebAddress(processHandle);

    /* get the address of ProcessParameters */
    if (!ReadProcessMemory(processHandle,
            &(((_PEB*) pebAddress)->ProcessParameters),
            &rtlUserProcParamsAddress,
            sizeof(PVOID), NULL))
    {
        printf("Could not read the address of ProcessParameters!\n");
        return GetLastError();
    }

    /* read the CommandLine UNICODE_STRING structure */
    if (!ReadProcessMemory(processHandle,
        &(((_RTL_USER_PROCESS_PARAMETERS*) rtlUserProcParamsAddress)->CommandLine),
        &commandLine, sizeof(commandLine), NULL))
    {
        printf("Could not read CommandLine!\n");
        return GetLastError();
    }

    /* allocate memory to hold the command line */
    commandLineContents = (WCHAR *)malloc(commandLine.Length);

    /* read the command line */
    if (!ReadProcessMemory(processHandle, commandLine.Buffer,
        commandLineContents, commandLine.Length, NULL))
    {
        printf("Could not read the command line string!\n");
        return GetLastError();
    }

    /* print it */
    /* the length specifier is in characters, but commandLine.Length is in bytes */
    /* a WCHAR is 2 bytes */
    printf("%.*S\n", commandLine.Length / 2, commandLineContents);
    CloseHandle(processHandle);
    free(commandLineContents);

    return 0;
}

更多细节,请查看获取进程的命令行

编辑(附加信息):

第一位作者说:

CreateRemoteThread只适用于与调用方相同会话ID的进程——在Windows Vista中,服务和其他系统进程运行在会话0中,而用户程序运行在更高的会话中。最好、最安全的方法是读取每个Windows进程中存在的一个结构。

对于OpenProcess也是同样的情况,你无法打开一个作为服务或者由SYSTEMLOCAL SERVICENETWORK SERVICE打开的进程,如果你是以一个用户(即使是管理员)的身份运行程序。

如果你的程序是服务,那么它可能已经使用local system account运行,所以不需要担心。 但如果不是,一个解决办法是使用psexec来启动它:

  1. 下载PSEXEC并解压到某个文件夹中。
  2. 以管理员身份打开提升的CMD提示符。
  3. 导航到你解压PSEXEC.EXE的文件夹
  4. 运行:PSEXEC -i -s -d CMD
  5. 你将会有一个新的CMD提示符打开。
  6. 在新的CMD提示符中键入以下内容证明你是谁:WHOAMI

你应该看到你是SYSTEM,现在你可以启动你的程序并查看所有进程的命令行。


4

你无法可靠地获取那些信息。有各种技巧可以尝试检索它,但不能保证目标进程没有已经破坏了内存的那一部分。Raymond Chen在The Old New Thing上讨论过这个问题。


谢谢你,太。这是一个有趣的链接。由于我只想检查我们自己进程的命令行(不故意更改它),所以我应该没问题。我需要命令行的原因是,我正在编写一个服务,确保一组在某个时间点运行或停止的进程处于定义的配置中。为了验证这一点,我将比较进程的exe文件路径和命令行。 - beutelfuchs
1
@beutelfuchs:在这种情况下,使用互斥锁或信号量会更好。 - Deanna
链接页面已移动:https://devblogs.microsoft.com/oldnewthing/20091125-00/?p=15923 - Miscreant
如果您想获取当前进程的命令行,GetCommandLine(链接 ANSI 版本,对于 Unicode/"multibyte"/"wide-char",请按照惯例查看 GetCommandLineW)是您需要的过程。 - Armen Michaeli

2

你可以将一个dll注入到外部进程的地址空间中,然后调用GetCommandLine函数。


其中,inject a dllGetCommandLine是链接,可能需要点击跳转到相关页面。

肯定是一种解决方案,但在我的情况下比Steve的方法更复杂。因为我需要使用IPC将“被窃取”的命令行传递回我的查询过程中。 - beutelfuchs

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