Answered by:
Getting a command line for another process

Question
-
First, please don't tell me I need to upgrade. Not an option at this point. A simple can or can't do and if so how would work.
What I need to do is find a way to get the command line that was used to start another process after I cycle through the list of running processes. I'd like to be able to do this on operating systems from Win2k through to current releases.
For example: Let's say I created an application that does many different things based on the command line. I'd like to cycle through the window list(I know how to do this part) an check the command line used to launch each process to see what command line options were sent to that process when it was started.
i.e.
process ID 1234, MyApp.exe some_argument
process ID 7894, MyApp.exe do_this then do_that
Now be able to, from another application, interrogate the system to find out what the command line for process ID 1234 and 7894 were.Friday, January 29, 2010 10:32 PM
Answers
-
Hello Wasabi_Sushi,
Expanding a little on Eric Haddan's good suggestion, the DLL injection method was first expounded by Jeffrey Richter (circa 1995) in his excllent book "Advanced Windows". This techique can be quite useful for accomplishing tasks like the one you described. Here is a summary :
1. Load a pre-written DLL and inject it into your target application.
1.1 This can be done via a clever combination of VirtualAllocEx(), CreateRemoteThread(), WriteProcessMemory(). The following link provides sample code to do this based on Jeffrey Richter's original code :
http://www.skullsecurity.org/wiki/index.php/.dll_Injection_and_Patching
The function to use is InjectLibrary().
1.2 The link above also contains a conjugal function EjectLibrary() to unload a DLL from the target application.
1.3 You need to pass to the InjectLibrary() function a handle to the target process. This can be retrieved via the OpenProcess() API. Among the flags you need to set for the first parameter to OpenProcess() are PROCESS_CREATE_THREAD and PROCESS_QUERY_INFORMATION.
1.4 Remember that the DLL needs to be located in a path that the target application can find.
2. When the pre-written DLL is loaded into the address space of the target application, its DllMain() function will be called with parameter DLL_PROCESS_ATTACH. Inside DllMain(), you can make a call to GetCommandLine() (as mentioned by Eric Haddan) to obtain the command line passed to the target application.
3. Now, what's left to do is to somehow pass the string returned by GetCommandLine() back to the original application (that initiated the DLL injection operation).
3.1 One way to do this is via file mapping (which is a form of memory sharing).
3.2 Basically, the original application creates a named file mapping object using CreateFileMapping(). It then maps a view of the file mapping object using MapViewOfFile() :
HANDLE hMapFile;hMapFile = CreateFileMapping
(
INVALID_HANDLE_VALUE, // Current file handle.
NULL, // Default security.
PAGE_READWRITE, // Read/write permission.
0, // Max. object size.
256, // Size of hFile.
"GetCommandLine" // Name of mapping object.
);
Note that by specifying INVALID_HANDLE_VALUE as the first parameter, we get the CreateFileMapping() API to create a file-mapping object of the specified size backed by the operating-system paging file rather than by a named file in the file system.
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS, // Read/write permission
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
memset (lpMapAddress, 0, 256);
The MapViewOfFile() API returns a pointer to a memory location that is sharable with other apps. This memory location can be directly managed with functions like memcpy(), memset(), etc. In my example code above I used the name "GetCommandLine" as the name of the file mapping object.
3.3 After the file mapping objet has been created successfully, the DLL is injected into the target app. The idea is that when the DLL is loaded into the target app's address space, it will determine the command line and then copy the command line string into the file mapping object. This is all done inside DllMain() so that to the original app, when the DLL has been loaded, the original app can immediately read its file mapping object to retrieve the command line string :
if (InjectLibrary(hProcess, "InjectionDLL.dll"))
{
EjectLibrary(hProcess, dwProcessId, "InjectionDLL.dll");
}
// At this time, lpMapAddress should contain the command line of the target app
// unless something went wrong inside DllMain().
3.4 Inside the DllMain() function of the injected DLL, the file mapping object is opened via OpenFileMapping(). The name of the object is specified so that the same object first created in the original app is referred :
HANDLE hMapFile = NULL;
hMapFile = OpenFileMapping
(
FILE_MAP_ALL_ACCESS,
FALSE,
"GetCommandLine"
);
3.5 A view of the mapping object must also be created via MapViewOfFile() :
LPVOID lpMapAddress = NULL;
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS,// Read/write permission.
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
3.6 The GetCommandLine() function can then be called to retrieve the command line string. This string is then copied into the shared memory address returned by MapViewOfFile() :
std::string strCommandLine;strCommandLine.resize(256, 0);
sprintf (&(strCommandLine[0]), "%s", GetCommandLine());
memcpy (lpMapAddress, (LPCVOID)(strCommandLine.c_str()), strlen(strCommandLine.c_str()));
3.7 Be sure to call UnmapViewOfFile() at the end of the whole operation for both the original map as well as the injected DLL :
UnmapViewOfFile(lpMapAddress);
Best of luck, Wasabi_Sushi,
- Bio.- Edited by Lim Bio Liong Sunday, January 31, 2010 10:13 AM Explain the use of INVALID_HANDLE_VALUE for CreateFileMapping().
- Proposed as answer by dumitru.pisarciuc Monday, February 1, 2010 8:39 PM
- Marked as answer by Wasabi_Sushi Tuesday, February 2, 2010 2:16 PM
Sunday, January 31, 2010 10:00 AM
All replies
-
You may find this useful:
http://blogs.msdn.com/oldnewthing/archive/2009/11/25/9928372.aspx
Phil Wilson- Proposed as answer by scorpion007 Saturday, January 30, 2010 12:52 AM
Saturday, January 30, 2010 12:30 AM -
You may find this useful:
http://blogs.msdn.com/oldnewthing/archive/2009/11/25/9928372.aspx
Phil Wilson
On first look, not very useful. I'll look at it again later.Saturday, January 30, 2010 2:06 AM -
Process Monitor from SysInternals(Microsoft) will do this. You will get a ton of information from all the processes running on your system, but if you right-click on the "MyApp.exe" in the process name column and select "Include MyApp.exe.." you will get rid of all the other programs running on your system. Then just find the different PIDs and double-click on them and they will show you the actuall command line in the "Process" tab.
Oops, sorry! Only supports XP SP2 and above- Edited by Eric Haddan Saturday, January 30, 2010 2:14 AM Added oops
Saturday, January 30, 2010 2:13 AM -
True. Only XP Pro and up.
I need to do this through a program I write using some API. I have to be able to see what the command line arguments to a given process is so I can tell what mode or which service it is running. A third party app won't do what I'm looking for.Saturday, January 30, 2010 3:57 AM -
Well, maybe you can use a function like GetCommandLine to get the information. It needs to be run from within the process so maybe you can look at either AppInit_DLLs or other DLL Injection methods to get your own code into another process. The command line is actually stored in the process's Process Environment Block (PEB) if you can figure out any other way to get to the information.Saturday, January 30, 2010 4:16 AM
-
Hello Wasabi_Sushi,
Expanding a little on Eric Haddan's good suggestion, the DLL injection method was first expounded by Jeffrey Richter (circa 1995) in his excllent book "Advanced Windows". This techique can be quite useful for accomplishing tasks like the one you described. Here is a summary :
1. Load a pre-written DLL and inject it into your target application.
1.1 This can be done via a clever combination of VirtualAllocEx(), CreateRemoteThread(), WriteProcessMemory(). The following link provides sample code to do this based on Jeffrey Richter's original code :
http://www.skullsecurity.org/wiki/index.php/.dll_Injection_and_Patching
The function to use is InjectLibrary().
1.2 The link above also contains a conjugal function EjectLibrary() to unload a DLL from the target application.
1.3 You need to pass to the InjectLibrary() function a handle to the target process. This can be retrieved via the OpenProcess() API. Among the flags you need to set for the first parameter to OpenProcess() are PROCESS_CREATE_THREAD and PROCESS_QUERY_INFORMATION.
1.4 Remember that the DLL needs to be located in a path that the target application can find.
2. When the pre-written DLL is loaded into the address space of the target application, its DllMain() function will be called with parameter DLL_PROCESS_ATTACH. Inside DllMain(), you can make a call to GetCommandLine() (as mentioned by Eric Haddan) to obtain the command line passed to the target application.
3. Now, what's left to do is to somehow pass the string returned by GetCommandLine() back to the original application (that initiated the DLL injection operation).
3.1 One way to do this is via file mapping (which is a form of memory sharing).
3.2 Basically, the original application creates a named file mapping object using CreateFileMapping(). It then maps a view of the file mapping object using MapViewOfFile() :
HANDLE hMapFile;hMapFile = CreateFileMapping
(
INVALID_HANDLE_VALUE, // Current file handle.
NULL, // Default security.
PAGE_READWRITE, // Read/write permission.
0, // Max. object size.
256, // Size of hFile.
"GetCommandLine" // Name of mapping object.
);
Note that by specifying INVALID_HANDLE_VALUE as the first parameter, we get the CreateFileMapping() API to create a file-mapping object of the specified size backed by the operating-system paging file rather than by a named file in the file system.
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS, // Read/write permission
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
memset (lpMapAddress, 0, 256);
The MapViewOfFile() API returns a pointer to a memory location that is sharable with other apps. This memory location can be directly managed with functions like memcpy(), memset(), etc. In my example code above I used the name "GetCommandLine" as the name of the file mapping object.
3.3 After the file mapping objet has been created successfully, the DLL is injected into the target app. The idea is that when the DLL is loaded into the target app's address space, it will determine the command line and then copy the command line string into the file mapping object. This is all done inside DllMain() so that to the original app, when the DLL has been loaded, the original app can immediately read its file mapping object to retrieve the command line string :
if (InjectLibrary(hProcess, "InjectionDLL.dll"))
{
EjectLibrary(hProcess, dwProcessId, "InjectionDLL.dll");
}
// At this time, lpMapAddress should contain the command line of the target app
// unless something went wrong inside DllMain().
3.4 Inside the DllMain() function of the injected DLL, the file mapping object is opened via OpenFileMapping(). The name of the object is specified so that the same object first created in the original app is referred :
HANDLE hMapFile = NULL;
hMapFile = OpenFileMapping
(
FILE_MAP_ALL_ACCESS,
FALSE,
"GetCommandLine"
);
3.5 A view of the mapping object must also be created via MapViewOfFile() :
LPVOID lpMapAddress = NULL;
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS,// Read/write permission.
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
3.6 The GetCommandLine() function can then be called to retrieve the command line string. This string is then copied into the shared memory address returned by MapViewOfFile() :
std::string strCommandLine;strCommandLine.resize(256, 0);
sprintf (&(strCommandLine[0]), "%s", GetCommandLine());
memcpy (lpMapAddress, (LPCVOID)(strCommandLine.c_str()), strlen(strCommandLine.c_str()));
3.7 Be sure to call UnmapViewOfFile() at the end of the whole operation for both the original map as well as the injected DLL :
UnmapViewOfFile(lpMapAddress);
Best of luck, Wasabi_Sushi,
- Bio.- Edited by Lim Bio Liong Sunday, January 31, 2010 10:13 AM Explain the use of INVALID_HANDLE_VALUE for CreateFileMapping().
- Proposed as answer by dumitru.pisarciuc Monday, February 1, 2010 8:39 PM
- Marked as answer by Wasabi_Sushi Tuesday, February 2, 2010 2:16 PM
Sunday, January 31, 2010 10:00 AM -
That VBScript is an example of how to get another process' command line using the Win32_Process WMI class. With a little thought you can use that with the System.Management classes (which are WMI) and with C# as in this example:
http://www.codeproject.com/KB/system/hardware_properties_c_.aspx
I apologize for not posting the exact C# code that will do this for you. I thought you'd figure it out using that article as a clue. You don't need code injection or anything fancy, just the standard System.Management classes.
Phil WilsonMonday, February 1, 2010 8:03 PM -
That VBScript is an example of how to get another process' command line using the Win32_Process WMI class. With a little thought you can use that with the System.Management classes (which are WMI) and with C# as in this example:
http://www.codeproject.com/KB/system/hardware_properties_c_.aspx
I apologize for not posting the exact C# code that will do this for you. I thought you'd figure it out using that article as a clue. You don't need code injection or anything fancy, just the standard System.Management classes.
Phil Wilson
I'd prefer to code this in C/C++ and remember it must run at least on Win2K, and 2003 Server.Monday, February 1, 2010 8:14 PM -
Hello Wasabi_Sushi,
Expanding a little on Eric Haddan's good suggestion, the DLL injection method was first expounded by Jeffrey Richter (circa 1995) in his excllent book "Advanced Windows". This techique can be quite useful for accomplishing tasks like the one you described. Here is a summary :
1. Load a pre-written DLL and inject it into your target application.
1.1 This can be done via a clever combination of VirtualAllocEx(), CreateRemoteThread(), WriteProcessMemory(). The following link provides sample code to do this based on Jeffrey Richter's original code :
http://www.skullsecurity.org/wiki/index.php/.dll_Injection_and_Patching
The function to use is InjectLibrary().
1.2 The link above also contains a conjugal function EjectLibrary() to unload a DLL from the target application.
1.3 You need to pass to the InjectLibrary() function a handle to the target process. This can be retrieved via the OpenProcess() API. Among the flags you need to set for the first parameter to OpenProcess() are PROCESS_CREATE_THREAD and PROCESS_QUERY_INFORMATION.
1.4 Remember that the DLL needs to be located in a path that the target application can find.
2. When the pre-written DLL is loaded into the address space of the target application, its DllMain() function will be called with parameter DLL_PROCESS_ATTACH. Inside DllMain(), you can make a call to GetCommandLine() (as mentioned by Eric Haddan) to obtain the command line passed to the target application.
3. Now, what's left to do is to somehow pass the string returned by GetCommandLine() back to the original application (that initiated the DLL injection operation).
3.1 One way to do this is via file mapping (which is a form of memory sharing).
3.2 Basically, the original application creates a named file mapping object using CreateFileMapping(). It then maps a view of the file mapping object using MapViewOfFile() :
HANDLE hMapFile;hMapFile = CreateFileMapping
(
INVALID_HANDLE_VALUE, // Current file handle.
NULL, // Default security.
PAGE_READWRITE, // Read/write permission.
0, // Max. object size.
256, // Size of hFile.
"GetCommandLine" // Name of mapping object.
);
Note that by specifying INVALID_HANDLE_VALUE as the first parameter, we get the CreateFileMapping() API to create a file-mapping object of the specified size backed by the operating-system paging file rather than by a named file in the file system.
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS, // Read/write permission
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
memset (lpMapAddress, 0, 256);
The MapViewOfFile() API returns a pointer to a memory location that is sharable with other apps. This memory location can be directly managed with functions like memcpy(), memset(), etc. In my example code above I used the name "GetCommandLine" as the name of the file mapping object.
3.3 After the file mapping objet has been created successfully, the DLL is injected into the target app. The idea is that when the DLL is loaded into the target app's address space, it will determine the command line and then copy the command line string into the file mapping object. This is all done inside DllMain() so that to the original app, when the DLL has been loaded, the original app can immediately read its file mapping object to retrieve the command line string :
if (InjectLibrary(hProcess, "InjectionDLL.dll"))
{
EjectLibrary(hProcess, dwProcessId, "InjectionDLL.dll");
}
// At this time, lpMapAddress should contain the command line of the target app
// unless something went wrong inside DllMain().
3.4 Inside the DllMain() function of the injected DLL, the file mapping object is opened via OpenFileMapping(). The name of the object is specified so that the same object first created in the original app is referred :
HANDLE hMapFile = NULL;
hMapFile = OpenFileMapping
(
FILE_MAP_ALL_ACCESS,
FALSE,
"GetCommandLine"
);
3.5 A view of the mapping object must also be created via MapViewOfFile() :
LPVOID lpMapAddress = NULL;
lpMapAddress = MapViewOfFile
(
hMapFile, // Handle to mapping object.
FILE_MAP_ALL_ACCESS,// Read/write permission.
0, // Max. object size.
0, // Size of hFile.
0 // Map entire file.
);
3.6 The GetCommandLine() function can then be called to retrieve the command line string. This string is then copied into the shared memory address returned by MapViewOfFile() :
std::string strCommandLine;strCommandLine.resize(256, 0);
sprintf (&(strCommandLine[0]), "%s", GetCommandLine());
memcpy (lpMapAddress, (LPCVOID)(strCommandLine.c_str()), strlen(strCommandLine.c_str()));
3.7 Be sure to call UnmapViewOfFile() at the end of the whole operation for both the original map as well as the injected DLL :
UnmapViewOfFile(lpMapAddress);
Best of luck, Wasabi_Sushi,
- Bio.Monday, February 1, 2010 8:16 PM -
Hello Wasabi_Sushi,
The calls to the VirtualAllocEx(), WriteProcessMemory() and CreateRemoteThread() APIs inside the InjectLibrary() function provided by the sample code in the above-mentioned link may fail if the "dwDesiredAccess" parameter in the call to the OpenProcess() API was inadequate.
I used the (PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION) flags.
If necessary, you may have to do some experiments with the flags.
Best Regards,
- Bio.Tuesday, February 2, 2010 5:14 AM -
Hello Wasabi_Sushi,
The calls to the VirtualAllocEx(), WriteProcessMemory() and CreateRemoteThread() APIs inside the InjectLibrary() function provided by the sample code in the above-mentioned link may fail if the "dwDesiredAccess" parameter in the call to the OpenProcess() API was inadequate.
I used the (PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION) flags.
If necessary, you may have to do some experiments with the flags.
Best Regards,
- Bio.
I've tried all that and it doesn't seem to work. The reason I don't think that it is working is that I've added MessageBox() calls in the DllMain of the DLL I wrote that is supposedly loaded by the other app. I don't see any message boxes popping up with the message coded in the DLL. I used the PROCESS_ALL_ACCESS in the call to OpenProcess() so I think I should have all of the flags turned on.Tuesday, February 2, 2010 1:30 PM -
That is another think with these DLLs. For the most part, you can only use functions from kernel32.dll in the DLL. There are exceptions, but you definitely can't use MessageBox in DllMain. If you want to get some kind of response, try using CreateFile and WriteFile to see what is happening. So with these files, all exported functions must be from kernel32.dll. If you don't know how to check for that, run the command-line "dumpbin /exports mydll.dll". You can use functions that are not in kernel32, but not while you are in the DllMain routine, but you need to use LoadLibrary/GetProcAddress to get the address of the function to run it.Tuesday, February 2, 2010 1:47 PM
-
And above all this, you can't even guarantee that the command line you get back is accurate.
All GetCommandLine does is get access to a conveniently initialised variable, so what you get is the contents of the string after it has been used by the program itself.
So how is this problematic? Let us use an example here.
TCHAR* cmdline;
size_t cmdlinelen;
cmdline = GetCommandLine();
cmdlinelen = _tcslen(cmdline);
memset(cmdline, 0, sizeof(TCHAR)*cmdlinelen);
cmdline = NULL;
cmdline = GetCommandLine();
_tprintf(_T("The command line is %s\n"), cmdline);
What would you get printed out with that?
So quite simply no matter what, you can't say for sure what you get is the exact command line used to call the application.
Visit my (not very good) blog at http://c2kblog.blogspot.com/Tuesday, February 2, 2010 2:06 PM -
I tried the writing to a file as well but no file is created.Tuesday, February 2, 2010 2:06 PM
-
I think I have it working now. It looks like it was my not using the virtual memory buffer correctly.
Thanks for the help.
Oh and Eric, the MessageBox() calls do work from DllMain.Tuesday, February 2, 2010 2:16 PM -
Congratulations. My mistake on that, if you use AppInit_DLL to load your DLL, then all of those rules apply:
"Because of their early loading, only API functions that are exported from Kernel32.dll are safe to use in the initialization of the AppInit DLLs."
http://support.microsoft.com/kb/197571Tuesday, February 2, 2010 3:21 PM -
In this case, the app being tested has been loaded for quite some time and is well past the initialization stages.Tuesday, February 2, 2010 3:36 PM