実行中のプロセス一覧を取得するタスクマネージャのようなアプリを作ろうとしています。
各プロセスの、実行コマンドライン文字列を取得する方法で不安な点があり、ご意見頂けると助かります。
環境: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を使う
//
// >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;
}
例えば、起動中のメモ帳のプロセスIDを引数に実行すると、以下のようなメッセージボックスが出ます。
"C:\WINDOWS\system32\notepad.exe" I:\ファイル名.txt
ただ、このソースコードが何をやっているのか?なぜこれで動くのか?大丈夫なのか?
初めて使う関数が多く、難しくてよくわからず漠然と不安です。
両者とも、プロセスを開いた(OpenProcess)後、ReadProcessMemoryでそのプロセスのメモリ上にある
コマンドライン文字列を取ってきていると思うのですが、そのやり方は異なっています。
この2つの方式に、動作環境などの違いはあるでしょうか?
例えば、WinXP SP3 以降でないと動作しない、Win2000でも動作する、など。
さらに、例えば対象のプロセスが終了してしまう可能性がある、などの危険性はないでしょうか?
また、このプログラムが何をやっているのか?もっと理解したいのですが、よい解説サイトが見つかりません。
・方式1では、VirtualQueryEx に、アドレス 0x00020498 を固定的に渡していますが、
このアドレス値はいったいどこから出てきたのか?どうやって知るのでしょうか?
・方式2で出てくる、PEB(Process Environment Block)とは何でしょうか?
解説サイトや、検索キーワードなど、参考情報を教えてもらえると助かります。