From many versions of same program.exe name how to determine it's arguments

Pages: 123
Ganado: I'm just repeating myself now. The C# code is just a shell/interface that calls into the C++ code. The information that you are looking for is within the .cpp (C++, not C#) files of the github project. Look at the GetProcessParameter function. Again, it is C++.

I hate banging my head on things in order to try to get things to work. So, I'll just ask you since you know stuff. It's simplier.

Question: When I compile the two files you talk about, I get the below errors. How would you get this thing to compile? But more importantly, do you have an example "call" that also compiles that I can stick in a main routine and demonstrates finding the parameters of some other running program?

Thanks anyway,
Mike

1
2
3
4
5
Error	2	error LNK1120: 1 unresolved externals	C:... 1	1	

Error	1	error LNK2019: unresolved external symbol _NtQueryInformationProcess@20 
referenced in function "struct _PEB * __cdecl ProcessHelper::GetPebAddress(void *)" 
(?GetPebAddress@ProcessHelper@@YAPAU_PEB@@PAX@Z)	C:\...\ProcessHelper.obj


ProcessHelper.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "ProcessHelper.h"

PPEB ProcessHelper::GetPebAddress(HANDLE hProcess) {

    PROCESS_BASIC_INFORMATION pbi = { 0 };

    if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr))) {
        return pbi.PebBaseAddress;
    }
    else {
        return nullptr;
    }
}

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_DRIVE_LETTER_CURDIR
//0x18 bytes (sizeof)
struct MY_RTL_DRIVE_LETTER_CURDIR
{
    USHORT Flags;                                                           //0x0
    USHORT Length;                                                          //0x2
    ULONG TimeStamp;                                                        //0x4
    struct _STRING DosPath;                                                 //0x8
};

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_CURDIR
//0x18 bytes (sizeof)
struct MY_CURDIR
{
    struct _UNICODE_STRING DosPath;                                         //0x0
    VOID* Handle;                                                           //0x10
};

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_USER_PROCESS_PARAMETERS
//0x3f0 bytes (sizeof)
struct MY_RTL_USER_PROCESS_PARAMETERS
{
    ULONG MaximumLength;                                                    //0x0
    ULONG Length;                                                           //0x4
    ULONG Flags;                                                            //0x8
    ULONG DebugFlags;                                                       //0xc
    VOID* ConsoleHandle;                                                    //0x10
    ULONG ConsoleFlags;                                                     //0x18
    VOID* StandardInput;                                                    //0x20
    VOID* StandardOutput;                                                   //0x28
    VOID* StandardError;                                                    //0x30
    struct MY_CURDIR CurrentDirectory;                                      //0x38
    struct _UNICODE_STRING DllPath;                                         //0x50
    struct _UNICODE_STRING ImagePathName;                                   //0x60
    struct _UNICODE_STRING CommandLine;                                     //0x70
    VOID* Environment;                                                      //0x80
    ULONG StartingX;                                                        //0x88
    ULONG StartingY;                                                        //0x8c
    ULONG CountX;                                                           //0x90
    ULONG CountY;                                                           //0x94
    ULONG CountCharsX;                                                      //0x98
    ULONG CountCharsY;                                                      //0x9c
    ULONG FillAttribute;                                                    //0xa0
    ULONG WindowFlags;                                                      //0xa4
    ULONG ShowWindowFlags;                                                  //0xa8
    struct _UNICODE_STRING WindowTitle;                                     //0xb0
    struct _UNICODE_STRING DesktopInfo;                                     //0xc0
    struct _UNICODE_STRING ShellInfo;                                       //0xd0
    struct _UNICODE_STRING RuntimeData;                                     //0xe0
    struct MY_RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];                //0xf0
};

int ProcessHelper::GetProcessParameter(DWORD dwProcId, LPWSTR& szParameter, ProcessParameter parameter) {
    szParameter = nullptr;
    auto rc = 0;

    auto hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcId);
    if (hProcess) {

        auto pPEBAddress = GetPebAddress(hProcess);
        if (pPEBAddress) {

            MY_RTL_USER_PROCESS_PARAMETERS* pRtlUserProcParamsAddress;
            SIZE_T nSizeRead = 0;
            if (ReadProcessMemory(hProcess, &(pPEBAddress->ProcessParameters),
                &pRtlUserProcParamsAddress, sizeof(pRtlUserProcParamsAddress), &nSizeRead) &&
                (nSizeRead == sizeof(pRtlUserProcParamsAddress))) {

                auto paramPtr = [parameter, pRtlUserProcParamsAddress]() -> PUNICODE_STRING
                {
                    switch (parameter)
                    {
                    case ProcessParameter::CommandLine:
                        return &pRtlUserProcParamsAddress->CommandLine;
                    case ProcessParameter::WorkingDirectory:
                        return &pRtlUserProcParamsAddress->CurrentDirectory.DosPath;
                    default:
                        return nullptr;
                    }
                }();
                UNICODE_STRING paramStr;
                if (ReadProcessMemory(hProcess, paramPtr,
                    &paramStr, sizeof(paramStr), &nSizeRead) && (nSizeRead == sizeof(paramStr))) {

                    szParameter = new WCHAR[paramStr.MaximumLength];
                    if (szParameter) {
                        memset(szParameter, 0, paramStr.MaximumLength);

                        if (ReadProcessMemory(hProcess, paramStr.Buffer,
                            szParameter, paramStr.Length, &nSizeRead) && (nSizeRead == paramStr.Length)) {
                            rc = 0;
                        }
                        else {
                            // couldn't read parameter string
                            delete[] szParameter;
                            szParameter = nullptr;
                            rc = -6;
                        }
                    }
                    else {
                        // couldn't allocate memory
                        rc = -5;
                    }
                }
                else {
                    // couldn't read parameter
                    rc = -4;
                }
            }
            else {
                // couldn't read process memory
                rc = -3;
            }
        }
        else {
            // couldn't get PEB address
            rc = -2;
        }
        CloseHandle(hProcess);
    }
    else {
        // couldn't open process
        rc = -1;
    }

    return rc;
}



ProcessHelper.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

#include <windows.h>
#include <winternl.h>

namespace ProcessHelper
{
    enum class ProcessParameter
    {
        CommandLine,
        WorkingDirectory,
    };

    int GetProcessParameter(DWORD dwProcId, LPWSTR& szCmdLine, ProcessParameter parameter);

    PPEB GetPebAddress(HANDLE hProcess);

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

} // namespace ProcessHelper 
Last edited on
Now that you mention it, that code appears to be fragile. I didn't spot the difference before. The call inside GetPebAddress needs to be loaded dynamically, which is what the Microsoft docs recommend as well.

1
2
3
4
5
6
PVOID CProcessHelper::GetPebAddress(HANDLE ProcessHandle){
	_NtQueryInformationProcess NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
	PROCESS_BASIC_INFORMATION pbi = { 0 };
	NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL);
	return pbi.PebBaseAddress;
}


This is not my code, it was publicly available online somewhere:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool CProcessHelper::GetProcessCommandLine(DWORD dwProcId, LPWSTR& szCmdLine){
	szCmdLine = NULL;

	HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | /* required for NtQueryInformationProcess */
		PROCESS_VM_READ, /* required for ReadProcessMemory */
		FALSE, dwProcId);
	if (!processHandle){ return false; }

	PVOID pebAddress = GetPebAddress(processHandle);

	PVOID rtlUserProcParamsAddress;
	if (!ReadProcessMemory(processHandle, &(((_PEB*)pebAddress)->ProcessParameters), &rtlUserProcParamsAddress, sizeof(PVOID), NULL)){
		CloseHandle(processHandle);
		return false;
	}

	UNICODE_STRING commandLine;
	if (!ReadProcessMemory(processHandle, &(((_RTL_USER_PROCESS_PARAMETERS*)rtlUserProcParamsAddress)->CommandLine), &commandLine, sizeof(commandLine), NULL)){
		CloseHandle(processHandle);
		return false;
	}

	szCmdLine = new WCHAR[commandLine.MaximumLength];
	memset(szCmdLine, 0, commandLine.MaximumLength);

	if (!ReadProcessMemory(processHandle, commandLine.Buffer, szCmdLine, commandLine.Length, NULL)){
		delete[] szCmdLine;
		CloseHandle(processHandle);
		return false;
	}

	CloseHandle(processHandle);
	return true;
}


You can call it like:
1
2
3
4
5
    DWORD process = /* ... */;

    LPWSTR cmdLine;
    CProcessHelper::GetProcessCommandLine(process, cmdLine);
    std::wcout << cmdLine << '\n';


Other code that might work as well (not tested): https://github.com/3gstudent/Homework-of-C-Language/blob/master/GetProcessCommandLine.cpp
Last edited on
Ganado,

I got it to compile. Do you have a complete example of how to call this code with notepad.exe or something like it on Windows?

This is the current ProcessHelper.cpp file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include "ProcessHelper.h"

PPEB ProcessHelper::GetPebAddress(HANDLE ProcessHandle){
    _NtQueryInformationProcess NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL);
    return pbi.PebBaseAddress;
}

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_DRIVE_LETTER_CURDIR
//0x18 bytes (sizeof)
struct MY_RTL_DRIVE_LETTER_CURDIR
{
    USHORT Flags;                                                           //0x0
    USHORT Length;                                                          //0x2
    ULONG TimeStamp;                                                        //0x4
    struct _STRING DosPath;                                                 //0x8
};

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_CURDIR
//0x18 bytes (sizeof)
struct MY_CURDIR
{
    struct _UNICODE_STRING DosPath;                                         //0x0
    VOID* Handle;                                                           //0x10
};

bool ProcessHelper::GetProcessCommandLine(DWORD dwProcId, LPWSTR& szCmdLine){
    szCmdLine = NULL;

    HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | /* required for NtQueryInformationProcess */
        PROCESS_VM_READ, /* required for ReadProcessMemory */
        FALSE, dwProcId);
    if (!processHandle){ return false; }

    PVOID pebAddress = GetPebAddress(processHandle);

    PVOID rtlUserProcParamsAddress;
    if (!ReadProcessMemory(processHandle, &(((_PEB*)pebAddress)->ProcessParameters), &rtlUserProcParamsAddress, sizeof(PVOID), NULL)){
        CloseHandle(processHandle);
        return false;
    }

    UNICODE_STRING commandLine;
    if (!ReadProcessMemory(processHandle, &(((_RTL_USER_PROCESS_PARAMETERS*)rtlUserProcParamsAddress)->CommandLine), &commandLine, sizeof(commandLine), NULL)){
        CloseHandle(processHandle);
        return false;
    }

    szCmdLine = new WCHAR[commandLine.MaximumLength];
    memset(szCmdLine, 0, commandLine.MaximumLength);

    if (!ReadProcessMemory(processHandle, commandLine.Buffer, szCmdLine, commandLine.Length, NULL)){
        delete[] szCmdLine;
        CloseHandle(processHandle);
        return false;
    }

    CloseHandle(processHandle);
    return true;
}


// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_USER_PROCESS_PARAMETERS
//0x3f0 bytes (sizeof)
struct MY_RTL_USER_PROCESS_PARAMETERS
{
    ULONG MaximumLength;                                                    //0x0
    ULONG Length;                                                           //0x4
    ULONG Flags;                                                            //0x8
    ULONG DebugFlags;                                                       //0xc
    VOID* ConsoleHandle;                                                    //0x10
    ULONG ConsoleFlags;                                                     //0x18
    VOID* StandardInput;                                                    //0x20
    VOID* StandardOutput;                                                   //0x28
    VOID* StandardError;                                                    //0x30
    struct MY_CURDIR CurrentDirectory;                                      //0x38
    struct _UNICODE_STRING DllPath;                                         //0x50
    struct _UNICODE_STRING ImagePathName;                                   //0x60
    struct _UNICODE_STRING CommandLine;                                     //0x70
    VOID* Environment;                                                      //0x80
    ULONG StartingX;                                                        //0x88
    ULONG StartingY;                                                        //0x8c
    ULONG CountX;                                                           //0x90
    ULONG CountY;                                                           //0x94
    ULONG CountCharsX;                                                      //0x98
    ULONG CountCharsY;                                                      //0x9c
    ULONG FillAttribute;                                                    //0xa0
    ULONG WindowFlags;                                                      //0xa4
    ULONG ShowWindowFlags;                                                  //0xa8
    struct _UNICODE_STRING WindowTitle;                                     //0xb0
    struct _UNICODE_STRING DesktopInfo;                                     //0xc0
    struct _UNICODE_STRING ShellInfo;                                       //0xd0
    struct _UNICODE_STRING RuntimeData;                                     //0xe0
    struct MY_RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];                //0xf0
};

int ProcessHelper::GetProcessParameter(DWORD dwProcId, LPWSTR& szParameter, ProcessParameter parameter) {
    szParameter = nullptr;
    auto rc = 0;

    auto hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcId);
    if (hProcess) {

        auto pPEBAddress = GetPebAddress(hProcess);
        if (pPEBAddress) {

            MY_RTL_USER_PROCESS_PARAMETERS* pRtlUserProcParamsAddress;
            SIZE_T nSizeRead = 0;
            SIZE_T dummy = 0;

            if (ReadProcessMemory(hProcess, &(pPEBAddress->ProcessParameters),
                &pRtlUserProcParamsAddress, sizeof(pRtlUserProcParamsAddress), &nSizeRead) &&
                (nSizeRead == sizeof(pRtlUserProcParamsAddress))) {

                auto paramPtr = [parameter, pRtlUserProcParamsAddress]() -> PUNICODE_STRING
                {
                    switch (parameter)
                    {
                        return &pRtlUserProcParamsAddress->CommandLine;
                    case ProcessParameter::WorkingDirectory:
                        return &pRtlUserProcParamsAddress->CurrentDirectory.DosPath;
                    default:
                        return nullptr;
                    }
                }();
                UNICODE_STRING paramStr;
                if (ReadProcessMemory(hProcess, paramPtr,
                    &paramStr, sizeof(paramStr), &nSizeRead) && (nSizeRead == sizeof(paramStr))) {

                    szParameter = new WCHAR[paramStr.MaximumLength];
                    if (szParameter) {
                        memset(szParameter, 0, paramStr.MaximumLength);

                        if (ReadProcessMemory(hProcess, paramStr.Buffer,
                            szParameter, paramStr.Length, &nSizeRead) && (nSizeRead == paramStr.Length)) {
                            rc = 0;
                        }
                        else {
                            // couldn't read parameter string
                            delete[] szParameter;
                            szParameter = nullptr;
                            rc = -6;
                        }
                    }
                    else {
                        // couldn't allocate memory
                        rc = -5;
                    }
                }
                else {
                    // couldn't read parameter
                    rc = -4;
                }
            }
            else {
                // couldn't read process memory
                rc = -3;
            }
        }
        else {
            // couldn't get PEB address
            rc = -2;
        }
        CloseHandle(hProcess);
    }
    else {
        // couldn't open process
        rc = -1;
    }

    return rc;
}


Last edited on
And, this is the ProcessHelpher.h file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma once

#include <windows.h>
#include <winternl.h>

namespace ProcessHelper
{
    enum class ProcessParameter
    {
        CommandLine,
        WorkingDirectory,
    };

    int GetProcessParameter(DWORD dwProcId, LPWSTR& szCmdLine, ProcessParameter parameter);
    bool GetProcessCommandLine(DWORD dwProcId, LPWSTR& szCmdLine);

    PPEB GetPebAddress(HANDLE ProcessHandle);

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

} // namespace ProcessHelper 
Without any success, I have been passing a PID and empty buffer to GetProcessCommandLine.

The buffer is defined as:

LPWSTR cmdLine;

I've been printing out cmdLine with printf (" Command Line is %s\n",cmdLine);

I can't test out stuff right now, but are you running as an administrator?
What is the return code 'rc'? It will give you more information as to which part is failing.
Last edited on
1
2
I can't test out stuff right now, but are you running as an administrator?
What is the return code 'rc'? It will give you more information as to which part is failing. 

I'm administrator when I run the command interpreter (cmd).

My pids are correct.

(1) For example, to try to get information about a program with known parameters I use:

int pid = getpid();

The return code from the routine gives me 1 and the associated cmdLine buffer has only "C" in it which might be the first character in about 105 characters that was used on the command line to start it.

(2) When I call the same routine with a pid (int) for a program that has no parameters the return code is 0. The cmdLine is printed out as null.

I invoke (1) first then (2). I define cmdLine only once.

This is how I call the routine:

returncode = ProcessHelper::GetProcessCommandLine(pid, cmdLine);
Last edited on
Interestingly enough, the file ProcessHelper.cpp has another routine that compiles.

(1) When I make the following call for my program that has known parameters:

returncode = ProcessHelper::GetProcessParameter(pid,cmdLine,ProcessHelper::ProcessParameter::CommandLine);

The return code is -4 and the buffer is null.

(2) When I make the following call for the program which don't have parameters:

returncode = ProcessHelper::GetProcessParameter(Pid,cmdLine,ProcessHelper::ProcessParameter::CommandLine);

The return code is -2 and the buffer is null.

A comment in routine GetProcessParameter says it returns -2 when it couldn't get the PEB address.

A comment in routine GetProcessParameter says it returns -4 when it couldn't read a parameter.
Last edited on
You can add even more error handling within the function. For -4 return code, it means ReadProcessMemory is returning 0 or null.

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory
If the function succeeds, the return value is nonzero.

If the function fails, the return value is 0 (zero). To get extended error information, call GetLastError.


Inside the else branch where you assign rc = -4, add
std::cout << "ReadProcessMemory error: " << GetLastError() << '\n';
(or printf equivalent with %d)

This will give a Windows error code, which can be cross-referenced with https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-

________________________________

This looks wrong:
1
2
3
4
5
6
7
8
                  switch (parameter)
                    {
                        return &pRtlUserProcParamsAddress->CommandLine;
                    case ProcessParameter::WorkingDirectory:
                        return &pRtlUserProcParamsAddress->CurrentDirectory.DosPath;
                    default:
                        return nullptr;
                    }


You should compile with warnings enabled!
main.cpp:143:77: warning: statement will never be executed [-Wswitch-unreachable]
                 auto paramPtr = [parameter, pRtlUserProcParamsAddress]() -> PUNICODE_STRING
                                                                             ^~~~~~~~~~~~~~~

...granted, it's not the best warning, because it only points to the lambda as a whole, but it still shows you that there may be a problem with your switch statement, which there is.

I believe you meant:
1
2
3
4
5
6
7
8
9
                    switch (parameter)
                    {
                    case ProcessParameter::CommandLine:
                        return &pRtlUserProcParamsAddress->CommandLine;
                    case ProcessParameter::WorkingDirectory:
                        return &pRtlUserProcParamsAddress->CurrentDirectory.DosPath;
                    default:
                        return nullptr;
                    }


And I don't believe you showed your main function?
It would look something like:
1
2
3
4
5
6
7
8
9
int main()
{
    int pid = 18596; // replace with your desired process id or user input
    
    LPWSTR cmd;
    ProcessHelper::GetProcessParameter((DWORD)pid, cmd, ProcessHelper::ProcessParameter::CommandLine);
    if (cmd)
        std::wcout << cmd << '\n';
}
Last edited on
-4 means the code can't read the memory of the referenced process. To read the memory of a process, the stipulated process must have PROCESS_VM_READ access.

Usually, a process doesn't grant this access and you need to acquire it by having the privilege SE_DEBUG_NAME ("SeDebugPrivilege") which needs to be granted to the user and then invoked in the program.

For this type of fragile windows code, you need all the ducks lined up properly.
Last edited on
(1) I made all the changes that you suggested. I also put in the print statement where ever the return code was being set.

After doing that, I no longer received any -4 return codes. It was because of the new switch statement.

But, when I then ran my program with a PID for a program that took arguments when it started:

(1a) Using GetProcessCommandLine() the return code was 0. The command line buffer only had "C" in it.

(1b) Without resetting the cmdline variable and using GetProcessParameter() the return code was 0 and the Windows Error code was 18. The command line buffer only had "C" in it.

(2a) Without resetting the cmdline variable and using GetProcessCommandLine() when I tested with a PID for a program without arguments the return code was -2. The command line buffer was null.

(2b)Without resetting the cmdline variable and using GetProcessParameter() when I tested with a PID for a program without arguments the return code was -2 and the Windows Error code was 18. The command line buffer was null.

FYI: The windows error code 18 indicates ERROR_NO_MORE_FILES, I.E.: There are no more files.

(3) Sidetracking: Before I made the change to the switch statement, I added the GetLastError () call only to where the return code was being set to -4. I ran it and got this:

Windows error code 299 which is indicates ERROR_PARTIAL_COPY.

Someone else was getting the 299 error:

https://stackoverflow.com/questions/34648061/function-readprocessmemory-keeps-returning-error-partial-copy
Show the place you call ProcessHelper::GetProcessParameter from, and how you are printing the retrieved information.

@seeplus:

Definitely good to consider it, but I don't see anything in the documentation that says SeDebugPrivilege is needed. (Totally possible I'm missing something.)

I tried it out with a user that doesn't have SeDebugPrivilege, and it still works fine.
The only restriction that I can see is that a non-elevated process cannot get the information from an elevated (protected) process.
Last edited on
@Ganado. The documentation for ReadProcessMemory() just says it requires PROCESS_VM_READ access. If you can open a process with this access, then great. If you can't, then you'll need to first acquire SeDebugPrivilege which gives you the ability to overcome any process access restrictions.

Normally processes are isolated from each other - to stop them interfering with one another. SeDubugPrivilege breaks down this isolation by the process holding this right.

It's one of the most powerful privileges (along with SeBackupPrivilege and SeRestorePrivilege - overwrite file/folder read permissions and write permissions).

I don't print out the Windows Error code directly in the GetProcessParameter() routine, I pass in a new variable (Windows_error_code) and set that variable.

Here is my code for the program that has arguments passed to it when it is started.

1
2
3
4
5
6
7
8
9
10
11
12
13
LPWSTR cmdLine;

std::string Program_With_Args = "abc.exe";

int pid = getpid();

returncode = ProcessHelper::GetProcessCommandLine((DWORD)pid, cmdLine);

printf ("ROUTINE_1 RETURN CODE = %d, Program Name = %s, pid = %d, and Command Line is %s\n", returncode, Program_With_Args, pid, cmdLine);

returncode = ProcessHelper::GetProcessParameter((DWORD)pid, cmdLine, ProcessHelper::ProcessParameter::CommandLine, &Windows_error_code);

printf("ROUTINE_2 RETURN CODE = %d, Windows Error Code = %d, Program Name = %s, pid = %d, and Command Line is %s\n", returncode, Windows_error_code, Program_With_Args, pid, cmdLine);
You need to be more careful with the string formatters for printf.

Also, turn on warnings!
main.cpp:243:12: warning: format '%s' expects argument of type 'char*', but argument 4 has type 'std::__cxx11::string' {aka 'std::__cxx11::basic_string<char>'} [-Wformat=]
     printf("ROUTINE_2 RETURN CODE = %d, Windows Error Code = %d, Program Name = %s, pid = %d, and Command Line is %s\n", returncode, Windows_error_code, Program_With_Args, pid, cmdLine);
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:243:12: warning: format '%s' expects argument of type 'char*', but argument 6 has type 'LPWSTR' {aka 'wchar_t*'} [-Wformat=]


printf with %s will not correctly print a wide string.

Try using %ls instead.
And turn on warnings :)
Last edited on
ProcessHelpher.cpp in two parts:

Part 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include "ProcessHelpher.h"

PPEB ProcessHelper::GetPebAddress(HANDLE ProcessHandle){
    _NtQueryInformationProcess NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
    PROCESS_BASIC_INFORMATION pbi = { 0 };
    NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL);
    return pbi.PebBaseAddress;
}

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_DRIVE_LETTER_CURDIR
//0x18 bytes (sizeof)
struct MY_RTL_DRIVE_LETTER_CURDIR
{
    USHORT Flags;                                                           //0x0
    USHORT Length;                                                          //0x2
    ULONG TimeStamp;                                                        //0x4
    struct _STRING DosPath;                                                 //0x8
};

// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_CURDIR
//0x18 bytes (sizeof)
struct MY_CURDIR
{
    struct _UNICODE_STRING DosPath;                                         //0x0
    VOID* Handle;                                                           //0x10
};

bool ProcessHelper::GetProcessCommandLine(DWORD dwProcId, LPWSTR& szCmdLine){
    szCmdLine = NULL;

    HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | /* required for NtQueryInformationProcess */
        PROCESS_VM_READ, /* required for ReadProcessMemory */
        FALSE, dwProcId);
    if (!processHandle){ return false; }

    PVOID pebAddress = GetPebAddress(processHandle);

    PVOID rtlUserProcParamsAddress;
    if (!ReadProcessMemory(processHandle, &(((_PEB*)pebAddress)->ProcessParameters), &rtlUserProcParamsAddress, sizeof(PVOID), NULL)){
        CloseHandle(processHandle);
        return false;
    }

    UNICODE_STRING commandLine;
    if (!ReadProcessMemory(processHandle, &(((_RTL_USER_PROCESS_PARAMETERS*)rtlUserProcParamsAddress)->CommandLine), &commandLine, sizeof(commandLine), NULL)){
        CloseHandle(processHandle);
        return false;
    }

    szCmdLine = new WCHAR[commandLine.MaximumLength];
    memset(szCmdLine, 0, commandLine.MaximumLength);

    if (!ReadProcessMemory(processHandle, commandLine.Buffer, szCmdLine, commandLine.Length, NULL)){
        delete[] szCmdLine;
        CloseHandle(processHandle);
        return false;
    }

    CloseHandle(processHandle);
    return true;
}


// Source: https://www.vergiliusproject.com/kernels/x64/Windows%20XP%20%7C%202003/SP2/_RTL_USER_PROCESS_PARAMETERS
//0x3f0 bytes (sizeof)
struct MY_RTL_USER_PROCESS_PARAMETERS
{
    ULONG MaximumLength;                                                    //0x0
    ULONG Length;                                                           //0x4
    ULONG Flags;                                                            //0x8
    ULONG DebugFlags;                                                       //0xc
    VOID* ConsoleHandle;                                                    //0x10
    ULONG ConsoleFlags;                                                     //0x18
    VOID* StandardInput;                                                    //0x20
    VOID* StandardOutput;                                                   //0x28
    VOID* StandardError;                                                    //0x30
    struct MY_CURDIR CurrentDirectory;                                      //0x38
    struct _UNICODE_STRING DllPath;                                         //0x50
    struct _UNICODE_STRING ImagePathName;                                   //0x60
    struct _UNICODE_STRING CommandLine;                                     //0x70
    VOID* Environment;                                                      //0x80
    ULONG StartingX;                                                        //0x88
    ULONG StartingY;                                                        //0x8c
    ULONG CountX;                                                           //0x90
    ULONG CountY;                                                           //0x94
    ULONG CountCharsX;                                                      //0x98
    ULONG CountCharsY;                                                      //0x9c
    ULONG FillAttribute;                                                    //0xa0
    ULONG WindowFlags;                                                      //0xa4
    ULONG ShowWindowFlags;                                                  //0xa8
    struct _UNICODE_STRING WindowTitle;                                     //0xb0
    struct _UNICODE_STRING DesktopInfo;                                     //0xc0
    struct _UNICODE_STRING ShellInfo;                                       //0xd0
    struct _UNICODE_STRING RuntimeData;                                     //0xe0
    struct MY_RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];                //0xf0
};
Last edited on
ProcessHelpher.cpp in two parts:

Part 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
int ProcessHelper::GetProcessParameter(DWORD dwProcId, LPWSTR& szParameter, ProcessParameter parameter, int *return_code) {
    szParameter = nullptr;
    auto rc = 0;

    auto hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcId);
    if (hProcess) {

        auto pPEBAddress = GetPebAddress(hProcess);
        if (pPEBAddress) {

            MY_RTL_USER_PROCESS_PARAMETERS* pRtlUserProcParamsAddress;
            SIZE_T nSizeRead = 0;

            if (ReadProcessMemory(hProcess, &(pPEBAddress->ProcessParameters),
                &pRtlUserProcParamsAddress, sizeof(pRtlUserProcParamsAddress), &nSizeRead) &&
                (nSizeRead == sizeof(pRtlUserProcParamsAddress))) {

                auto paramPtr = [parameter, pRtlUserProcParamsAddress]() -> PUNICODE_STRING
                {
                    switch (parameter)
                    {
                    case ProcessParameter::CommandLine:
                        return &pRtlUserProcParamsAddress->CommandLine;
                    case ProcessParameter::WorkingDirectory:
                        return &pRtlUserProcParamsAddress->CurrentDirectory.DosPath;
                    default:
                        return nullptr;
                    }
                }();
                UNICODE_STRING paramStr;
                if (ReadProcessMemory(hProcess, paramPtr,
                    &paramStr, sizeof(paramStr), &nSizeRead) && (nSizeRead == sizeof(paramStr))) {

                    szParameter = new WCHAR[paramStr.MaximumLength];
                    if (szParameter) {
                        memset(szParameter, 0, paramStr.MaximumLength);

                        if (ReadProcessMemory(hProcess, paramStr.Buffer,
                            szParameter, paramStr.Length, &nSizeRead) && (nSizeRead == paramStr.Length)) {
                            rc = 0;

                            *return_code = GetLastError();
                        }
                        else {
                            // couldn't read parameter string
                            delete[] szParameter;
                            szParameter = nullptr;
                            rc = -6;

                            *return_code = GetLastError();
                        }
                    }
                    else {
                        // couldn't allocate memory
                        rc = -5;

                        *return_code = GetLastError();
                    }
                }
                else {
                    // couldn't read parameter
                    rc = -4;

                    *return_code = GetLastError();
                }
            }
            else {
                // couldn't read process memory
                rc = -3;

                *return_code = GetLastError();
            }
        }
        else {
            // couldn't get PEB address
            rc = -2;
        }
        CloseHandle(hProcess);
    }
    else {
        // couldn't open process
        rc = -1;

        *return_code = GetLastError();
    }

    return rc;
}
Last edited on
Ganado: Other code that might work as well (not tested): https://github.com/3gstudent/Homework-of-C-Language/blob/master/GetProcessCommandLine.cpp

I tried to get this to compile but the below include wouldn't compile:

#include <Ntsecapi.h>

If I take out that include then the code containing the following wouldn't compile:

_NtQueryInformationProcess

An alternative to this is the following: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process?redirectedfrom=MSDN

But, I don't know how to convert the code to use it.

Reference: https://stackoverflow.com/questions/10929745/alternatives-to-ntqueryinformationprocess-to-get-commandline
Last edited on
Did you see my previous post? The reason it only prints 1 character is because the wrong printf specifier is being used. I think it will work if you print %ls (that's a lowercase L).

And don't use printf %s with a std::string. Pass in the string's .c_str() instead.
Last edited on
I missed your earlier post.

Using the "ls" specifier worked for the program that was started with arguments to the program.

But, for the case of starting a program without arguments the cmdLine variable is null.
Pages: 123