Getting another process command line in Windows
Asked Answered
I

6

15

I am trying to get another process' command-line parameters (on WinXP 32bit).

I do the following:

hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);

BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;

BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;

BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;

After the first call, pbi.UniqueProcessID is correct.

But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.

I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.

Can anybody help?

On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.

Instantly answered 30/6, 2011 at 6:4 Comment(5)
Perhaps you have 0 for the process id in the OpenProcess call, or something like that?Grandnephew
No. hProcess is correct and pbi I get is also correct.Instantly
Maybe the command line of your own process is the same as the command line of the other process? :-)Romeu
No. It isn't. I'm trying to find javaw process from c++ program.Instantly
You may want to read this: blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspxBorkowski
W
4

Duplicate of How to query a running process for its parameters list? (Windows, C++) , so I'll just copy my answer from there over here:

You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.

Wring answered 1/7, 2011 at 14:12 Comment(2)
The question is not a duplicate; it asks why the specific code does not work. The fact that info in other process' PEB is unreliable is interesting, but it does not answer the question.Ki
I suppose the oldnewthing is quite outdated. Here I read the opposite: #24755344 The original Commandline stays in the PEB and GetCommandLine() returns only a copy of it!Rydder
K
13

It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer. The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):

        LONG status = NtQueryInformationProcess(hProcess,
                                                0,
                                                pinfo,
                                                sizeof(PVOID)*6,
                                                NULL);
        PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
        PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
        BOOL result = ReadProcessMemory(hProcess,
                                        ppeb,
                                        ppebCopy,
                                        sizeof(PEB),
                                        NULL);

        PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
        PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
            (PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
        result = ReadProcessMemory(hProcess,
                                   pRtlProcParam,
                                   pRtlProcParamCopy,
                                   sizeof(RTL_USER_PROCESS_PARAMETERS),
                                   NULL);
        PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
        USHORT len =  pRtlProcParamCopy->CommandLine.Length;
        PWSTR wBufferCopy = (PWSTR)malloc(len);
        result = ReadProcessMemory(hProcess,
                                   wBuffer,
                                   wBufferCopy, // command line goes here
                                   len,
                                   NULL);

Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.

Ki answered 15/11, 2012 at 23:38 Comment(4)
Thanks for help, but I've already found a solution in another question. As far as I remember, your code looks a bit better (there is no magic constants etc.).Instantly
This code is really very ugly and cryptic. Why don't you use ProcessBasicInformation instead of zero. Why don't you use sizeof(PROCESS_BASIC_INFORMATION) instead of sizeof(PVOID)*6 ?Rydder
Please note: You need to add terminating NULL character to wBufferCopy .Venenose
Hi, it's me, from 11 years in the future. This answer helped me figuring it out. Thank you!Xanthous
D
7

I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.

I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).

The final working code:

#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>

const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).

int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
    HRESULT hr = 0;
    IWbemLocator         *WbemLocator  = NULL;
    IWbemServices        *WbemServices = NULL;
    IEnumWbemClassObject *EnumWbem  = NULL;

    //initializate the Windows security
    hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
    hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);

    //connect to the WMI
    hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
    //Run the WQL Query
    hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);

    qDebug() << "Got here." << (void*)hr;
    // Iterate over the enumerator
    if (EnumWbem != NULL) {
        IWbemClassObject *result = NULL;
        ULONG returnedCount = 0;

        while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
            VARIANT ProcessId;
            VARIANT CommandLine;
            VARIANT ExecutablePath;

            // access the properties
            hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
            hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
            hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);

            if (ProcessId.uintVal == pid)
            {
                *commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
                *executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.

                qDebug() << *commandLine << *executable;
            }

            result->Release();
        }
    }

    // Release the resources
    EnumWbem->Release();
    WbemServices->Release();
    WbemLocator->Release();

    CoUninitialize();
    //getchar();

    return(0);
}

and in my Qt project file (.pro) I link to the following libraries:

LIBS += -lole32 -lwbemuuid
Daumier answered 19/11, 2013 at 21:10 Comment(2)
This code misses several error checks and leaks the memory for the CommandLine and ExecutablePath BSTRs. However, using WMI is the only reliable approach to getting command line parameters, so +1 from me.Countable
You can use a WHERE clause in the WMI query for the processid instead of iterating over the whole process set, see my adapted answer below.Ranita
R
4

You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.

In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.

Romeu answered 30/6, 2011 at 8:27 Comment(1)
I've edited the code into the post. Also, I marked line (first call to ZwReadVirtualMemory) which fails. I'm getting error code 122 (insufficient buffer). But BytesNeeded doesn't change it's value.Instantly
W
4

Duplicate of How to query a running process for its parameters list? (Windows, C++) , so I'll just copy my answer from there over here:

You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.

Wring answered 1/7, 2011 at 14:12 Comment(2)
The question is not a duplicate; it asks why the specific code does not work. The fact that info in other process' PEB is unreliable is interesting, but it does not answer the question.Ki
I suppose the oldnewthing is quite outdated. Here I read the opposite: #24755344 The original Commandline stays in the PEB and GetCommandLine() returns only a copy of it!Rydder
M
1

You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.

Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.

Marcela answered 30/6, 2011 at 14:5 Comment(2)
It looks like one does need ZwReadVirtualMemory or some equivalent of that.Ki
Indeed, PebBaseAddress points to memory in the target process, so you do have to call ReadProcessMemory (or its Native API equivalent) to get to that data.Gasconade
R
0

I adapted @brucecent's answer to use a WHERE clause, not require Qt and not leak the retrieved string.

#include <cstdlib>
#include <iostream>

#define WIN32_LEAN_AND_MEAN
#define _WIN32_DCOM

#ifdef __CYGWIN__
#define sprintf_s sprintf
#endif

#include <windows.h>
#include <comutil.h>
#include <stringapiset.h>
#include <wbemidl.h>

int
main(int argc, char **argv)
{
        int                      pid = argv[1] ? std::atoi(argv[1]) : GetCurrentProcessId();
        HRESULT                  hr = E_FAIL;
        IWbemLocator            *wbem_locator  = NULL;
        IWbemServices           *wbem_services = NULL;
        IEnumWbemClassObject    *enum_wbem  = NULL;

        CoInitializeEx(0, COINIT_MULTITHREADED);
        CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
        CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &wbem_locator);

        wbem_locator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &wbem_services);
        wchar_t *query = new wchar_t[4096];
        swprintf(query, 4096, L"select commandline from win32_process where processid = %d", pid);
        wbem_services->ExecQuery(L"WQL", query, WBEM_FLAG_FORWARD_ONLY, NULL, &enum_wbem);
        delete[] query;

        if (enum_wbem != NULL) {
                IWbemClassObject *result = NULL;
                ULONG returned_count = 0;

                if((hr = enum_wbem->Next(WBEM_INFINITE, 1, &result, &returned_count)) == S_OK) {
                        VARIANT process_id;
                        VARIANT command_line;

                        result->Get(L"CommandLine", 0, &command_line, 0, 0);

                        wchar_t         *command_line_utf16 = command_line.bstrVal;
                        size_t           size = WideCharToMultiByte(CP_UTF8, 0, command_line_utf16, -1, NULL, 0, NULL, NULL) + 1;
                        char            *command_line_utf8 = new char[size];

                        WideCharToMultiByte(CP_UTF8, 0, command_line_utf16, -1, command_line_utf8, size, NULL, NULL);

                        SysFreeString(command_line_utf16);

                        std::cout << pid << ": " << command_line_utf8 << std::endl;

                        delete command_line_utf8;

                        result->Release();
                }
        }
}

Compile for MSVC with:

cl .\show-process.cpp /Fe:show-process.exe ole32.lib oleaut32.lib wbemuuid.lib

, and for MinGW with:

g++ -fpermissive -Wno-write-strings -O2 show-process.cpp -o show-process.exe -lole32 -loleaut32 -lwbemuuid

.

You can find this example, as well as the same one but for the parent process id in my repo here.

Ranita answered 19/8 at 22:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.