Georg
Georg

Reputation: 421

Getting another process command line in Windows

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.

Upvotes: 16

Views: 33759

Answers (6)

Rafael Kitover
Rafael Kitover

Reputation: 1087

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

#include <cwchar>
#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>

// The code for the WMI query to retrieve the commandline is from:
// 
// https://stackoverflow.com/a/20082113/262458
// .

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

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

    wbem_locator->ConnectServer(L"ROOT\\CIMV2", nullptr, nullptr, nullptr, 0, nullptr, nullptr, &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, nullptr, &enum_wbem);
    delete[] query;

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

        if(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, nullptr, 0, nullptr, nullptr) + 1;
            char* command_line_utf8 = new char[size];

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

            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.

Upvotes: 0

Jon
Jon

Reputation: 3065

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.

Upvotes: 4

bruceceng
bruceceng

Reputation: 2182

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

Upvotes: 7

glagolig
glagolig

Reputation: 1120

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.

Upvotes: 13

Steve Townsend
Steve Townsend

Reputation: 54148

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.

Upvotes: 1

Frerich Raabe
Frerich Raabe

Reputation: 94329

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.

Upvotes: 4

Related Questions