14

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Georg
  • 401
  • 1
  • 5
  • 20

5 Answers5

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.

glagolig
  • 1,100
  • 1
  • 12
  • 28
  • 1
    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.). – Georg Nov 16 '12 at 06:02
  • 3
    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 ? – Elmue Aug 11 '15 at 04:14
  • Please note: You need to add terminating `NULL` character to wBufferCopy . – Sam Sep 12 '17 at 12:53
6

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
bruceceng
  • 1,844
  • 18
  • 23
  • 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. – GHH Oct 23 '15 at 11:31
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.

Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • 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. – Georg Jun 30 '11 at 10:10
4

Duplicate of How to query a running process for it's 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.

Community
  • 1
  • 1
Jon
  • 3,065
  • 1
  • 19
  • 29
  • 3
    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. – glagolig Nov 15 '12 at 22:34
  • 1
    I suppose the oldnewthing is quite outdated. Here I read the opposite: http://stackoverflow.com/questions/24754844/modifying-command-line-arguments-for-getcommandline?answertab=active#tab-top The original Commandline stays in the PEB and GetCommandLine() returns only a copy of it! – Elmue Aug 11 '15 at 04:24
0

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.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140