Get process command line

There is one officially supported way of getting command line from other processes, that is through WMI Win32_Process class. Here is a command that will display command line and pid of running processes:

WMIC PROCESS get Commandline,Processid

It is also possible to get command line of other processes by reading their memory and track down the information through path PEB -> ProcessParameters -> CommandLine. It involves using unsupported API or structures, and on 64-bit machines extra care needs to be taken for pointer size and structure alignment. Here is the code that can be built and run from 32-bit or 64-bit processes:

#include "stdafx.h"
#include <stddef.h>
#include <Windows.h>
#include <Winternl.h>

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

#ifdef _WIN64
	typedef struct _UNICODE_STRING_32 {
			USHORT Length;
			USHORT MaximumLength;
			UINT Buffer;
		} UNICODE_STRING_32;
	typedef struct _RTL_USER_PROCESS_PARAMETERS_32 {
		BYTE Reserved1[16];
		UINT32 Reserved2[10];
		UNICODE_STRING_32 ImagePathName;
		UNICODE_STRING_32 CommandLine;
	} RTL_USER_PROCESS_PARAMETERS_32;
#else
	typedef struct _UNICODE_STRING_64 {
			USHORT Length;
			USHORT MaximumLength;
			UINT32 pad;
			UINT64 Buffer;
		} UNICODE_STRING_64;
	typedef struct _PROCESS_BASIC_INFORMATION_64 {
		UINT64 Reserved1;
		UINT64 PebBaseAddress;
		UINT64 Reserved2[2];
		UINT64 UniqueProcessId;
		UINT64 Reserved3;
	} PROCESS_BASIC_INFORMATION_64;
	typedef struct _RTL_USER_PROCESS_PARAMETERS_64 {
		BYTE Reserved1[16];
		UINT64 Reserved2[10];
		UNICODE_STRING_64 ImagePathName;
		UNICODE_STRING_64 CommandLine;
	} RTL_USER_PROCESS_PARAMETERS_64;

	typedef NTSTATUS(NTAPI *PFN_NtWow64ReadVirtualMemory64)(
		HANDLE ProcessHandle,
		UINT64 BaseAddress,
		PVOID Buffer,
		ULONG64 Size,
		PULONG64 NumberOfBytesRead);

	PFN_NtQueryInformationProcess g_pfnNtQueryInformationProcess64 = NULL;
	PFN_NtWow64ReadVirtualMemory64 g_pfnWow64ReadVirtualMemory64 = NULL;
#endif

PFN_NtQueryInformationProcess g_pfnNtQueryInformationProcess = NULL;
SYSTEM_INFO g_sysinfo = { 0 };

void get_process_info_initialize()
{
	g_pfnNtQueryInformationProcess = (PFN_NtQueryInformationProcess)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryInformationProcess");
#ifndef _WIN64
	g_pfnNtQueryInformationProcess64 = (PFN_NtQueryInformationProcess)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtWow64QueryInformationProcess64");
	g_pfnWow64ReadVirtualMemory64 = (PFN_NtWow64ReadVirtualMemory64)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtWow64ReadVirtualMemory64");
#endif

	GetNativeSystemInfo(&g_sysinfo);
}

LONG get_process_info_by_ntqueryinformationprocess(HANDLE hprocess, PWSTR *info, DWORD pi_class, DWORD info_max_size)
{
	LONG error = 0;
	PCHAR buffer = 0;
	do
	{
		DWORD info_size = 0;
		error = g_pfnNtQueryInformationProcess(hprocess, pi_class, 0, 0, &info_size);
		if (info_size == 0 || info_size > info_max_size)
			break;

		buffer = (PCHAR)malloc(info_size);
		if (buffer == 0)
		{
			error = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		error = g_pfnNtQueryInformationProcess(hprocess, pi_class, buffer, info_size, &info_size);
		if(!NT_SUCCESS(error))
			break;

		PUNICODE_STRING str = (PUNICODE_STRING)buffer;
		size_t size = str->Length + sizeof(WCHAR);
		PWSTR temp_str = (PWSTR)malloc(size);
		if (temp_str == 0)
		{
			error = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}
		memset(temp_str, 0, size);
		memcpy(temp_str, str->Buffer, str->Length);
		*info = temp_str;

		error = 0;
	}while(0);
	if (buffer != 0)
		free(buffer);
	return error;
}

template <class unicode_string_type>
LONG read_process_memory_unicode_string(HANDLE hprocess, LPCVOID remote_address, PWSTR *output)
{
	LONG error = 0;
	do
	{
		unicode_string_type str = { 0 };
		ReadProcessMemory(hprocess, remote_address, &str, sizeof(str), 0);
		error = GetLastError();
		if (str.Length == 0 || str.Buffer == 0)
			break;

		size_t size = str.Length + sizeof(WCHAR);
		PWSTR temp_str = (PWSTR)malloc(size);
		if (temp_str == 0)
		{
			error = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		memset(temp_str, 0, size);
		ReadProcessMemory(hprocess, (PCHAR)str.Buffer, temp_str, str.Length, 0);
		*output = temp_str;

		error = 0;
	} while (0);
	return error;
}

LONG get_process_info_by_peb_native(HANDLE hprocess, PWSTR *image, PWSTR *command_line)
{
	LONG error = 0;
	do
	{
		PROCESS_BASIC_INFORMATION pbi = {0};
		error = g_pfnNtQueryInformationProcess(hprocess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
		if(pbi.PebBaseAddress == 0)
			break;

		SIZE_T process_parameter = 0;
		ReadProcessMemory(hprocess, (PCHAR)pbi.PebBaseAddress + offsetof(PEB, ProcessParameters), &process_parameter, sizeof(process_parameter), 0);
		error = GetLastError();
		if (process_parameter == 0)
			break;

		if (image != 0)
			error = read_process_memory_unicode_string<UNICODE_STRING>(hprocess, (PCHAR)process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS, ImagePathName), image);
		
		if (command_line != 0)
			error = read_process_memory_unicode_string<UNICODE_STRING>(hprocess, (PCHAR)process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine), command_line);
	}while(0);
	return error;
}

#ifdef _WIN64
#if 1
LONG get_process_info_by_peb_to_wow64(HANDLE hprocess, PWSTR *image, PWSTR *command_line)
{
	LONG error = 0;
	do
	{
		UINT64 peb32 = 0;
		error = g_pfnNtQueryInformationProcess(hprocess, 26 /*ProcessWow64Information*/, &peb32, sizeof(peb32), NULL);
		if(peb32 == 0)
			break;

		UINT64 process_parameter = 0;
		ReadProcessMemory(hprocess, (PCHAR)peb32 + 0x10, &process_parameter, sizeof(process_parameter), 0);
		error = GetLastError();
		if (process_parameter == 0)
			break;

		if (image != 0)
			error = read_process_memory_unicode_string<UNICODE_STRING_32>(hprocess, (PCHAR)process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS_32, ImagePathName), image);

		if (command_line != 0)
			error = read_process_memory_unicode_string<UNICODE_STRING_32>(hprocess, (PCHAR)process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS_32, CommandLine), command_line);
	}while(0);
	return error;
}
#endif
#endif

#ifndef _WIN64
template <typename unicode_string_type>
LONG read_virtual_memory_from_wow64_unicode_string(HANDLE hprocess, UINT64 remote_address, PWSTR *output)
{
	LONG error = 0;
	do
	{
		unicode_string_type str = { 0 };
		g_pfnWow64ReadVirtualMemory64(hprocess, remote_address, &str, sizeof(str), 0);
		error = GetLastError();
		if (str.Length == 0 || str.Buffer == 0)
			break;

		size_t size = str.Length + sizeof(WCHAR);
		PWSTR temp_str = (PWSTR)malloc(size);
		if (temp_str == 0)
		{
			error = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		memset(temp_str, 0, size);
		g_pfnWow64ReadVirtualMemory64(hprocess, str.Buffer, temp_str, str.Length, 0);
		*output = temp_str;

		error = 0;
	} while (0);
	return error;
}

LONG get_process_info_by_peb_from_wow64(HANDLE hprocess, PWSTR *image, PWSTR *command_line)
{
	LONG error = 0;
	do
	{
		PROCESS_BASIC_INFORMATION_64 pbi = {0};
		error = g_pfnNtQueryInformationProcess64(hprocess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
		if(pbi.PebBaseAddress == 0)
			break;

		UINT64 process_parameter = 0;
		g_pfnWow64ReadVirtualMemory64(hprocess, (UINT64)(pbi.PebBaseAddress + 0x20), &process_parameter, sizeof(process_parameter), 0);
		error = GetLastError();
		if(process_parameter == 0)
			break;

		if (image != 0)
			error = read_virtual_memory_from_wow64_unicode_string<UNICODE_STRING_64>(hprocess, process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS_64, ImagePathName), image);
		
		if(command_line != 0)
			error = read_virtual_memory_from_wow64_unicode_string<UNICODE_STRING_64>(hprocess, process_parameter + offsetof(RTL_USER_PROCESS_PARAMETERS_64, CommandLine), command_line);
	}while(0);
	return error;
}
#endif

LONG get_process_commandline(DWORD pid, BOOL *is32bit, LPWSTR *image, LPWSTR *command_line)
{
	LONG error = 0;
	HANDLE hprocess = NULL;
	do
	{
		DWORD access[] = { PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ,
			PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
			PROCESS_QUERY_LIMITED_INFORMATION };

		for (size_t n = 0; n < _countof(access); ++n)
		{
			hprocess = OpenProcess(access[n], FALSE, pid);
			error = GetLastError();
			if (hprocess != NULL)
				break;
		}
		if (NULL == hprocess)
			break;

#ifdef _WIN64
		*is32bit = FALSE;
#else
		*is32bit = TRUE;
#endif
		IsWow64Process(hprocess, is32bit);

#ifdef _WIN64
		{
			if (*is32bit)
				error = get_process_info_by_peb_to_wow64(hprocess, image, command_line);
			else
				error = get_process_info_by_peb_native(hprocess, image, command_line);
		}
#else
		{
			if (*is32bit || g_sysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
				error = get_process_info_by_peb_native(hprocess, image, command_line);
			else
				error = get_process_info_by_peb_from_wow64(hprocess, image, command_line);
		}
#endif

		if (image == 0)
			error = get_process_info_by_ntqueryinformationprocess(hprocess, image, 43 /* ProcessImageFileNameWin32 */, 2048);

		if (command_line == 0)
			error = get_process_info_by_ntqueryinformationprocess(hprocess, command_line, 60 /* ProcessCommandLineInformation */, 2048);
	} while (0);
	if (hprocess != NULL)
		CloseHandle(hprocess);
	return error;
}

Here is the testing code:

// cmdline.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <memory>
#include <map>
#include <stack>
#include <algorithm>

#include <Windows.h>
#include <Psapi.h>
#include <tlhelp32.h>
#pragma comment (lib, "Psapi.lib")

void get_process_info_initialize();
LONG get_process_commandline(DWORD pid, BOOL *is32bit, LPWSTR *image, LPWSTR *command_line);

struct _PROCESS_INFO;
typedef std::map<DWORD, _PROCESS_INFO*> processlist;
typedef std::stack<_PROCESS_INFO*> processtack;

typedef struct _PROCESS_INFO
{
	DWORD pid;
	BOOL is32bit;
	PWSTR image;
	PWSTR command_line;
	processlist child_processes;
}PROCESS_INFO, *PPROCESS_INFO;

PPROCESS_INFO process_alloc();
void process_free(PPROCESS_INFO process);

PPROCESS_INFO process_alloc()
{
	PPROCESS_INFO process = new(std::nothrow)PROCESS_INFO;
	if (process != 0)
	{
		process->pid = 0;
		process->is32bit = 0;
		process->image = 0;
		process->command_line = 0;
	}
	return process;
}

void process_free(PPROCESS_INFO process)
{
	processtack st;
	st.push(process);
	while (!st.empty())
	{
		process = st.top();
		st.pop();
		std::for_each(process->child_processes.begin(), process->child_processes.end(), [&st](processlist::value_type& val){ st.push(val.second); });

		if (process->image != 0)
			free(process->image);
		if (process->command_line != 0)
			free(process->command_line);
		delete process;
	}
}

#ifndef _WIN64
BOOL is_running_as_wow64()
{
	BOOL is_wow64 = FALSE;
	typedef BOOL(WINAPI *PFN_IsWow64Process) (HANDLE, PBOOL);
	PFN_IsWow64Process pfnIsWow64Process = (PFN_IsWow64Process)GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process");
	if (pfnIsWow64Process != NULL)
		pfnIsWow64Process(GetCurrentProcess(), &is_wow64);
	return is_wow64;
}
#endif

PPROCESS_INFO get_process_list()
{
	PPROCESS_INFO root = process_alloc();
	do
	{
		if (root == 0)
			break;

		HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
		if (INVALID_HANDLE_VALUE == hProcessSnap)
			break;

		processlist processes;

		PROCESSENTRY32 pe32 = { 0 };
		pe32.dwSize = sizeof(PROCESSENTRY32);
		Process32First(hProcessSnap, &pe32);
		do
		{
			if (pe32.th32ProcessID == 0)
				continue;

			PPROCESS_INFO process = process_alloc();
			if (process == NULL)
				break;

			process->pid = pe32.th32ProcessID;
			PWSTR path = pe32.szExeFile;

			MODULEENTRY32 me32 = { 0 };
			me32.dwSize = sizeof(MODULEENTRY32);
			HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pe32.th32ProcessID);
			if (INVALID_HANDLE_VALUE != hModuleSnap && Module32First(hModuleSnap, &me32))
				path = me32.szExePath;

			get_process_commandline(process->pid, &process->is32bit, &process->image, &process->command_line);

			if (process->image == 0)
				process->image = _wcsdup(path);

			if (INVALID_HANDLE_VALUE != hModuleSnap)
				CloseHandle(hModuleSnap);

			processlist::iterator itor = processes.find(pe32.th32ParentProcessID);
			if (itor == processes.end())
			{
				root->child_processes.insert(std::make_pair(pe32.th32ProcessID, process));
			}
			else
			{
				itor->second->child_processes.insert(std::make_pair(pe32.th32ProcessID, process));
			}
			processes.insert(std::make_pair(pe32.th32ProcessID, process));
		} while (Process32Next(hProcessSnap, &pe32));

		CloseHandle(hProcessSnap);
	} while (0);
	return root;
}

void dump_process_list(const processlist& processes)
{
	std::wstring indent;
	processtack st;
	std::for_each(processes.rbegin(), processes.rend(), [&st](const processlist::value_type& val){ st.push(val.second); });
	while (!st.empty())
	{
		const PPROCESS_INFO process = st.top();
		st.pop();
		if (process == 0)
		{
			indent.resize(indent.size() - 1);
			continue;
		}
		wprintf(indent.c_str());
		wprintf(L"pid:%d image=%s, cmdline=%s\n\n", process->pid, process->image, process->command_line);

		indent += L'\t';
		st.push(0);
		std::for_each(process->child_processes.rbegin(), process->child_processes.rend(), [&st](const processlist::value_type& val){ st.push(val.second); });
	}
}

void enable_debug_privilege(LPCWSTR priviledge)
{
	HANDLE token = 0;
	OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token);
	if (token != 0)
	{
		TOKEN_PRIVILEGES privileges = { 0 };
		privileges.PrivilegeCount = 1;
		privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
		LookupPrivilegeValue(0, priviledge, &privileges.Privileges[0].Luid);
		AdjustTokenPrivileges(token, FALSE, &privileges, 0, 0, 0);
		CloseHandle(token);
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	enable_debug_privilege(SE_DEBUG_NAME);
	get_process_info_initialize();
	PPROCESS_INFO root = get_process_list();
	if (root != 0)
	{
		dump_process_list(root->child_processes);
		process_free(root);
	}
	return 0;
}
Advertisements

Posted on June 22, 2015, in Uncategorized. Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: