[Win32]他プロセスのコマンドライン取得

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
たろ

[Win32]他プロセスのコマンドライン取得

#1

投稿記事 by たろ » 14年前

お世話になります。
実行中のプロセス一覧を取得するタスクマネージャのようなアプリを作ろうとしています。
各プロセスの、実行コマンドライン文字列を取得する方法で不安な点があり、ご意見頂けると助かります。

環境:WinXP SP3 Home, VC2008Express, BCC551, Win32API, C言語

ネットを探し回って、私の環境ではとりあえず動いてる感じのプログラムを作ることはできました。
いろいろ方式があるようですが、以下2つの方式のどちらかを使おうかと思っています。

方式1.VirtuslQueryExを使ってプロセスの特定アドレスのメモリを読み取る

コード:

//
//	指定プロセスIDのコマンドライン文字列を取得する。
//
//	方式1:VirtualQueryEx/ReadProcessMemory
//
//	>bcc32 ProcessCmdLineAlloc1.c
//	>cl ProcessCmdLineAlloc1.c
//
//	> ProcessCmdLineAlloc1.exe プロセスID
//
//	参考サイト
//	Cross process Environment Vars 
//	http://forum.sysinternals.com/cross-process-environment-vars_topic629_post1691.html
//	accessing m_lpCmdLine (or GetCommandLine())
//	http://us.generation-nt.com/answer/accessing-lpcmdline-getcommandline-help-15068982.html
//
//	上の2サイトのコードは似ているが、最初にVirtualQueryExするアドレスが
//	0x00020000と0x00020498で異なっている。前者だと取得できない場合があったので後者を使う。
//
//	smss.exeでReadProcessMemoryが299エラーになる。
//	NtQueryInformationProcess方式は成功する。
//
#pragma comment( lib, "user32.lib" )	// MessageBox
#pragma comment( lib, "advapi32.lib" )	// AdjustTokenPrivilegesなど
#include <windows.h>
#include <stdio.h>

LPWSTR		ProcessCmdLineAlloc( HANDLE hProcess );
DWORD		GetSystemPageSize( void );
BOOL		TokenPrivilege( LPTSTR PrivilegeName, BOOL IsEnable );

int main( int argc, char* argv[] )
{
	HANDLE	hProcess;
	LPWSTR	cmdline;

	if( argc < 2 ) return 0;
	// 特権有効
	TokenPrivilege( SE_DEBUG_NAME, TRUE );
	// 1つ目の引数のプロセスIDを開く
	hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, atoi(argv[1]) );
	if( !hProcess )
	{
		printf("OpenProcessエラー%u\n",GetLastError());
		return 0;
	}
	// コマンドライン文字列取得
	cmdline = ProcessCmdLineAlloc( hProcess );
	CloseHandle( hProcess );
	// 表示
	if( cmdline )
	{
		MessageBoxW(NULL,cmdline,L"結果",0);
		HeapFree( GetProcessHeap(), 0, cmdline );
	}
	return 0;
}
//
//	指定プロセスハンドルのコマンドライン文字列取得
//
LPWSTR ProcessCmdLineAlloc( HANDLE hProcess )
{
	#define ALIGN_DWORD(x)		( (x & 0xFFFFFFFC) ? (x & 0xFFFFFFFC) + sizeof(DWORD) : x )
	#define BLOCK_ADDRESS		(LPVOID)0x00020498
	LPBYTE						lpBuffer=NULL, lpPos=NULL;
    MEMORY_BASIC_INFORMATION	mbi;
	DWORD						SystemPageSize = GetSystemPageSize();
	DWORD						dwRead;
	LPWSTR						cmdline;

	// Get Command Line Block
	// At offset 0x00020498 is the process current directory followed by
	// the system PATH. After that is the process full command line, followed
	// by the exe name and the windows station it's running on.

	// First of all, use VirtualQuery to get the start of the memory block.
	//if( !VirtualQueryEx( hProcess, (LPVOID)0x00020000, &mbi, sizeof(mbi) ) )
	if( !VirtualQueryEx( hProcess, BLOCK_ADDRESS, &mbi, sizeof(mbi) ) )
	{
		printf("VirtualQueryExエラー%u\n",GetLastError());
		return NULL;
	}
	// Allocate one on the heap to retrieve a full page of memory.
	lpBuffer = (LPBYTE)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, SystemPageSize );
	if( !lpBuffer )
	{
		printf("HeapAlloc(%u)エラー\n",SystemPageSize);
		return NULL;
	}
	// Read memory begining at the start of the page.
	if( !ReadProcessMemory( hProcess, mbi.BaseAddress, (LPVOID)lpBuffer, SystemPageSize, &dwRead ) )
	{
		printf("ReadProcessMemoryエラー%u\n",GetLastError());
		HeapFree( GetProcessHeap(), 0, lpBuffer );
		return NULL;
	}
	// Now we've got the buffer on our side of the fence. First, we know
	// that the PATH block will be 0x498 bytes after the start of the page.
	// lpPos points to a string containing the current directory and path.

	// Align to start of block, this may seem un-necessary but the BaseAddress may change system to system.
	lpPos = lpBuffer + ((DWORD)BLOCK_ADDRESS - (DWORD)mbi.BaseAddress);
	// Skip programs current directory and path
	lpPos += (wcslen((LPWSTR)lpPos) + 1) * sizeof(WCHAR);
	// Aligned on a DWORD boundary skip it, and copy the next string into buffer and null terminate it.
	lpPos = (LPBYTE)ALIGN_DWORD((DWORD)lpPos);
	lpPos += (wcslen((LPWSTR)lpPos) + 1) * sizeof(WCHAR);
	// hack: Sometimes, there will be another '\0' at this position if that's so, skip it
	if ( *lpPos=='\0' ) lpPos += sizeof(WCHAR);
	// now we have the actual command line copy it to the buffer
	cmdline = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen((LPWSTR)lpPos)+1) * sizeof(WCHAR) );
	if( !cmdline )
	{
		printf("HeapAlloc(%u)エラー\n",(wcslen((LPWSTR)lpPos)+1)*sizeof(WCHAR));
		HeapFree( GetProcessHeap(), 0, lpBuffer );
		return NULL;
	}
	wcscpy( cmdline, (LPWSTR)lpPos );
	// clean up
	HeapFree( GetProcessHeap(), 0, lpBuffer );
	return cmdline;
}
//
//	システムページサイズ取得
//
DWORD GetSystemPageSize( void )
{
	SYSTEM_INFO	sysinfo;
	GetSystemInfo( &sysinfo );
	return sysinfo.dwPageSize;
}
//
//	プロセス特権の有効無効を設定する
//
BOOL TokenPrivilege( LPTSTR PrivilegeName, BOOL IsEnable )
{
	TOKEN_PRIVILEGES	tpv;
	HANDLE				hToken;
	LUID				luid;
	BOOL				err;
	// まず、OpenProcessTokenにGetCurrentProcessの戻り値を指定して、現在のプロセスのトークンを取得します。
	// このとき、AdjustTokenPrivilegesの呼び出しが必要になる関係上、TOKEN_ADJUST_PRIVILEGESを指定しています。
	if( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES |TOKEN_QUERY, &hToken ) )
	{
		printf("OpenProcessTokenエラー%u\n",GetLastError());
		return FALSE;
	}
	// 続いて、LookupPrivilegeValueで特権名から関連するLUIDを取得し、	
	if( !LookupPrivilegeValue( NULL, PrivilegeName, &luid ) )
	{
		printf("LookupPrivilegeValueエラー%u\n",GetLastError());
		goto ERR;
	}
	// これをTOKEN_PRIVILEGES構造体に指定します。指定する特権の数は1つであるため、PrivilegeCountは1であり、
	// IsEnableにはTRUEが設定されているため、AttributesにはSE_PRIVILEGE_ENABLEDが指定されます。
	tpv.PrivilegeCount				= 1;
	tpv.Privileges[0].Luid			= luid;
	tpv.Privileges[0].Attributes	= IsEnable ? SE_PRIVILEGE_ENABLED : 0;
	// AdjustTokenPrivilegesの呼び出しで特権が有効になります。
	if( !AdjustTokenPrivileges( hToken, FALSE, &tpv, 0, NULL, NULL ) || (err=GetLastError())!=ERROR_SUCCESS )
	{
		printf("AdjustTokenPrivilegesエラー\n");
		goto ERR;
	}
	// 注意点は、AdjustTokenPrivileges の戻り値が特権を実際に有効にできたかどうかを表わさないという点です。
	// この関数は、引数や構造体の指定に問題がなければ TRUE を返すため、特権を有効にできたかどうかは
	// GetLastError を通じて確認する必要があります。
	if( (err=GetLastError())!=ERROR_SUCCESS )
	{
		printf("AdjustTokenPrivilegesエラー%u\n", err);
		goto ERR;
	}
	// トークンハンドル閉じ
	CloseHandle( hToken );
	return TRUE;
 ERR:
	CloseHandle( hToken );
	return FALSE;
}
方式2.ntdll.dllのNtQueryInformationProcessを利用してプロセスのメモリを読み取る

コード:

//
//	方式2:ntdll.dllのNtQueryInformationProcessを使う
//	
//	>bcc32 ProcessCmdLineAlloc2.c
//	>cl ProcessCmdLineAlloc2.c
//
//	> ProcessCmdLineAlloc2.exe プロセスID
//
//	参考サイト
//	THE CODE PROJECT - Get Process Info with NtQueryInformationProcess
//	http://www.codeproject.com/KB/threads/GetNtProcessInfo.aspx
//
#pragma comment( lib, "user32.lib" )	// MessageBox
#pragma comment( lib, "advapi32.lib" )	// AdjustTokenPrivilegesなど
#include <windows.h>
#include <stdio.h>

//-----------------------------------------------------------------------------------------------
//
//	NtQueryInformationProcessに必要な定義
//
#define NTSTATUS			LONG
#define STATUS_SUCCESS		((NTSTATUS)0x00000000L) // ntsubauth

// SDK/v6.0A/Include/winternl.h
typedef struct {
    USHORT	Length;
    USHORT	MaximumLength;
    PWSTR	Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

// SDK/v6.0A/Include/winternl.h
typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation = 0,
    ProcessWow64Information = 26
} PROCESSINFOCLASS;

// Used in PEB struct
typedef ULONG	PPS_POST_PROCESS_INIT_ROUTINE;

// Used in PEB struct
typedef struct _PEB_LDR_DATA {
	BYTE		Reserved1[8];
	PVOID		Reserved2[3];
	LIST_ENTRY	InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

// Used in PEB struct
typedef struct _RTL_USER_PROCESS_PARAMETERS {
	BYTE			Reserved1[16];
	PVOID			Reserved2[10];
	UNICODE_STRING	ImagePathName;
	UNICODE_STRING	CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

// Used in PROCESS_BASIC_INFORMATION struct
typedef struct _PEB {
	BYTE							Reserved1[2];
	BYTE							BeingDebugged;
	BYTE							Reserved2[1];
	PVOID							Reserved3[2];
	PPEB_LDR_DATA					Ldr;
	PRTL_USER_PROCESS_PARAMETERS	ProcessParameters;
	BYTE							Reserved4[104];
	PVOID							Reserved5[52];
	PPS_POST_PROCESS_INIT_ROUTINE	PostProcessInitRoutine;
	BYTE							Reserved6[128];
	PVOID							Reserved7[1];
	ULONG							SessionId;
} PEB, *PPEB;

// Used with NtQueryInformationProcess
typedef struct _PROCESS_BASIC_INFORMATION {
    LONG		ExitStatus;
    PPEB		PebBaseAddress;
    ULONG_PTR	AffinityMask;
    LONG		BasePriority;
    ULONG_PTR	UniqueProcessId;
    ULONG_PTR	InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

//	NtQueryInformationProcess関数ポインタ型
typedef NTSTATUS (NTAPI *PNtQueryInformationProcess)
(
		IN	HANDLE				ProcessHandle,
		IN	PROCESSINFOCLASS	ProcessInformationClass,
		OUT	PVOID				ProcessInformation,
		IN	ULONG				ProcessInformationLength,
		OUT	PULONG				ReturnLength	OPTIONAL
);

//-----------------------------------------------------------------------------------------------
//
//	関数プロトタイプ
//
BOOL						NTDLLInitialize( void );
void						NTDLLFinalize( void );
PROCESS_BASIC_INFORMATION*	ProcessBasicInformationAlloc( HANDLE hProcess );
LPWSTR						ProcessCmdLineAlloc( HANDLE hProcess );
BOOL						TokenPrivilege( LPTSTR PrivilegeName, BOOL IsEnable );

//-----------------------------------------------------------------------------------------------
//
//	グローバル変数
//
HINSTANCE					NtDll			= NULL;		// ntdll.dllハンドル
PNtQueryInformationProcess	pNtInfoProcess	= NULL;		// NtQueryInformationProcess関数ポインタ

int main( int argc, char* argv[] )
{
	LPWSTR	cmdline;
	HANDLE	hProcess;

	if( argc < 2 ) return 0;
	// ntdll.dllロード
	if( !NTDLLInitialize() ) return 0;
	// 特権有効
	TokenPrivilege( SE_DEBUG_NAME, TRUE );
	// 1つ目の引数のプロセスIDを開く
	hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |PROCESS_VM_READ, FALSE, atoi(argv[1]) );
	if( !hProcess )
	{
		printf("OpenProcessエラー%u\n",GetLastError());
		return 0;
	}
	// コマンドライン文字列取得
	cmdline = ProcessCmdLineAlloc( hProcess );
	CloseHandle( hProcess );
	// 表示
	if( cmdline )
	{
		MessageBoxW(NULL,cmdline,L"結果",0);
		HeapFree( GetProcessHeap(), 0, cmdline );
	}
	NTDLLFinalize();
	return 0;
}
//
//	NTDLL初期化
//
BOOL NTDLLInitialize( void )
{
	NtDll = LoadLibrary( "ntdll.dll" );
	if( NtDll==NULL )
	{
		printf("ntdll.dllロードエラー%u\n",GetLastError());
		return FALSE;
	}
	pNtInfoProcess = (PNtQueryInformationProcess)GetProcAddress(NtDll,"NtQueryInformationProcess");
	if( !pNtInfoProcess )
	{
		printf("DLL関数ロードエラー\n",GetLastError());
		return FALSE;
	}
	return TRUE;
}
//
//	NTDLL終了
//
void NTDLLFinalize( void )
{
	if( NtDll )
	{
		FreeLibrary( NtDll );
		NtDll = NULL;
	}
	pNtInfoProcess = NULL;
}
//
//	指定プロセスハンドルのコマンドライン文字列取得
//
LPWSTR ProcessCmdLineAlloc( HANDLE hProcess )
{
	PROCESS_BASIC_INFORMATION*	pbi;
	PEB							peb;
	RTL_USER_PROCESS_PARAMETERS peb_upp;
	DWORD						dwRead	= 0;
	WCHAR*						cmdline	= NULL;

	// NtQueryInformationProcess
	pbi = ProcessBasicInformationAlloc( hProcess );
	if( !pbi ) return NULL;

	// Read Process Environment Block (PEB)
	if( pbi->PebBaseAddress==NULL )
	{
		printf("PEBアドレスがNULLです\n");
		goto fin;
	}
	ZeroMemory(&peb, sizeof(peb));
	if( !ReadProcessMemory( hProcess, pbi->PebBaseAddress, &peb, sizeof(peb), &dwRead ) )
	{
		printf("ReadProcessMemory(PEB)エラー%u\n",GetLastError());
		goto fin;
	}
	// if PEB read, try to read Process Parameters
	dwRead = 0;
	ZeroMemory(&peb_upp, sizeof(peb_upp));
	if( !ReadProcessMemory( hProcess, peb.ProcessParameters,
							&peb_upp, sizeof(RTL_USER_PROCESS_PARAMETERS), &dwRead ) )
	{
		printf("ReadProcessMemory(USER_PROCESS_PARAMETERS)エラー%u\n",GetLastError());
		goto fin;
	}
	// We got Process Parameters, is CommandLine filled in
	if( !peb_upp.CommandLine.Length )
	{
		printf("コマンドラインは空文字列です\n");
		goto fin;
	}
	// Yes, try to read CommandLine
	cmdline = (WCHAR*)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, peb_upp.CommandLine.Length + sizeof(WCHAR) );
	if( !cmdline )
	{
		printf("HeapAlloc(%u)エラー\n",peb_upp.CommandLine.Length + sizeof(WCHAR));
		goto fin;
	}
	// If memory was allocated, continue
	dwRead = 0;
	if( !ReadProcessMemory(hProcess, peb_upp.CommandLine.Buffer, cmdline, peb_upp.CommandLine.Length, &dwRead) )
	{
		printf("ReadProcessMemory(CommandLineBuffer)エラー%u\n",GetLastError());
		HeapFree( GetProcessHeap(), 0, cmdline );
		cmdline = NULL;
		goto fin;
	}
	// NULL終端
	cmdline[HeapSize(GetProcessHeap(),0,cmdline)/sizeof(WCHAR)-1] = L'\0';
 fin:
	if( pbi ) HeapFree( GetProcessHeap(), 0, pbi );
	return cmdline;
}
//
//	プロセスハンドルからPROCESS_BASIC_INFORMATION取得
//
PROCESS_BASIC_INFORMATION*
ProcessBasicInformationAlloc( HANDLE hProcess )
{
	PROCESS_BASIC_INFORMATION*	pbi;
	DWORD						size = sizeof(PROCESS_BASIC_INFORMATION);
	DWORD						sizeNeeded = 0;
	NTSTATUS					status;

	if( !pNtInfoProcess ) return NULL;
 retry:
	// バッファ確保
	pbi = (PROCESS_BASIC_INFORMATION*)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, size );
	if( !pbi )
	{
		printf("HeapAlloc(%u)エラー\n",size);
		return NULL;
	}
	// NtQueryInformationProcess
	status = pNtInfoProcess( hProcess, ProcessBasicInformation, pbi, size, &sizeNeeded );
	if( status != STATUS_SUCCESS )
	{
		if( size < sizeNeeded )
		{
			HeapFree( GetProcessHeap(), 0, pbi );
			size = sizeNeeded;
			goto retry;
		}
		printf("NtQueryInformationProcess不明なエラー%u\n",status);
		return NULL;
	}
	// 成功
	return pbi;
}
//
//	プロセス特権の有効無効を設定する
//
BOOL TokenPrivilege( LPTSTR PrivilegeName, BOOL IsEnable )
{
	TOKEN_PRIVILEGES	tpv;
	HANDLE				hToken;
	LUID				luid;
	BOOL				err;
	// まず、OpenProcessTokenにGetCurrentProcessの戻り値を指定して、現在のプロセスのトークンを取得します。
	// このとき、AdjustTokenPrivilegesの呼び出しが必要になる関係上、TOKEN_ADJUST_PRIVILEGESを指定しています。
	if( !OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES |TOKEN_QUERY, &hToken ) )
	{
		printf("OpenProcessTokenエラー%u\n",GetLastError());
		return FALSE;
	}
	// 続いて、LookupPrivilegeValueで特権名から関連するLUIDを取得し、	
	if( !LookupPrivilegeValue( NULL, PrivilegeName, &luid ) )
	{
		printf("LookupPrivilegeValueエラー%u\n",GetLastError());
		goto ERR;
	}
	// これをTOKEN_PRIVILEGES構造体に指定します。指定する特権の数は1つであるため、PrivilegeCountは1であり、
	// IsEnableにはTRUEが設定されているため、AttributesにはSE_PRIVILEGE_ENABLEDが指定されます。
	tpv.PrivilegeCount				= 1;
	tpv.Privileges[0].Luid			= luid;
	tpv.Privileges[0].Attributes	= IsEnable ? SE_PRIVILEGE_ENABLED : 0;
	// AdjustTokenPrivilegesの呼び出しで特権が有効になります。
	if( !AdjustTokenPrivileges( hToken, FALSE, &tpv, 0, NULL, NULL ) || (err=GetLastError())!=ERROR_SUCCESS )
	{
		printf("AdjustTokenPrivilegesエラー\n");
		goto ERR;
	}
	// 注意点は、AdjustTokenPrivileges の戻り値が特権を実際に有効にできたかどうかを表わさないという点です。
	// この関数は、引数や構造体の指定に問題がなければ TRUE を返すため、特権を有効にできたかどうかは
	// GetLastError を通じて確認する必要があります。
	if( (err=GetLastError())!=ERROR_SUCCESS )
	{
		printf("AdjustTokenPrivilegesエラー%u\n", err);
		goto ERR;
	}
	// トークンハンドル閉じ
	CloseHandle( hToken );
	return TRUE;
 ERR:
	CloseHandle( hToken );
	return FALSE;
}
ソースはCUIコマンドプログラムで、引数にプロセスIDを指定して実行すると、コマンドライン文字列を取得します。
例えば、起動中のメモ帳のプロセスIDを引数に実行すると、以下のようなメッセージボックスが出ます。

  "C:\WINDOWS\system32\notepad.exe" I:\ファイル名.txt

ただ、このソースコードが何をやっているのか?なぜこれで動くのか?大丈夫なのか?
初めて使う関数が多く、難しくてよくわからず漠然と不安です。

両者とも、プロセスを開いた(OpenProcess)後、ReadProcessMemoryでそのプロセスのメモリ上にある
コマンドライン文字列を取ってきていると思うのですが、そのやり方は異なっています。

この2つの方式に、動作環境などの違いはあるでしょうか?
例えば、WinXP SP3 以降でないと動作しない、Win2000でも動作する、など。
さらに、例えば対象のプロセスが終了してしまう可能性がある、などの危険性はないでしょうか?

また、このプログラムが何をやっているのか?もっと理解したいのですが、よい解説サイトが見つかりません。

・方式1では、VirtualQueryEx に、アドレス 0x00020498 を固定的に渡していますが、
 このアドレス値はいったいどこから出てきたのか?どうやって知るのでしょうか?
・方式2で出てくる、PEB(Process Environment Block)とは何でしょうか?

解説サイトや、検索キーワードなど、参考情報を教えてもらえると助かります。

“C言語何でも質問掲示板” へ戻る