リモートプロセスのコマンドラインパラメータを取得する

自分のプロセスのコマンドラインを取得するのは非常に簡単だ。main関数が存在するならargc, argvで取得できるし、そうでなければ、__argv, __argv, (__wargv/__targv)で取得することもできる。また、APIとしては、GetCommandLineというものもある。好んで使う理由は見あたらないけど。

しかしながら、リモートプロセスのコマンドラインとなると、とたんに敷居が高くなる。プロセスハンドルを持っていても、GetRemoteCommandLineというAPIは見あたらないのでどうしようもない。となると、考えられる方法としては、

  • CreateRemoteThreadなどで相手プロセス内でGetCommandLineを呼び出す
  • 相手のプロセスのメモリを直接読み込む

という2つのうちのいずれかを選択することになる。一つ目の案は、大げさすぎるのと、相手プロセスに送り込むスタブコードを考えるのが面倒という2つの問題があるので、現実的には、二つ目の奴を選択すべきだろう。ということで、ググって適当なところからパクってきたコードを修正したコードが次の通り。

#include <locale.h>
#include <stdio.h>

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

#ifndef NT_ERROR
#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)
#endif

DWORD GetRemoteCommandLineW(HANDLE hProcess, LPWSTR pszBuffer, UINT bufferLength)
{
  struct RTL_USER_PROCESS_PARAMETERS_I
  {
    BYTE Reserved1[16];
    PVOID Reserved2[10];
    UNICODE_STRING ImagePathName;
    UNICODE_STRING CommandLine;
  };
  
  struct PEB_INTERNAL
  {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    struct PEB_LDR_DATA* Ldr;
    RTL_USER_PROCESS_PARAMETERS_I* ProcessParameters;
    BYTE Reserved4[104];
    PVOID Reserved5[52];
    struct PS_POST_PROCESS_INIT_ROUTINE* PostProcessInitRoutine;
    BYTE Reserved6[128];
    PVOID Reserved7[1];
    ULONG SessionId;
  };
  
  typedef NTSTATUS (NTAPI* NtQueryInformationProcessPtr)(
    IN HANDLE ProcessHandle,
    IN PROCESSINFOCLASS ProcessInformationClass,
    OUT PVOID ProcessInformation,
    IN ULONG ProcessInformationLength,
    OUT PULONG ReturnLength OPTIONAL);
  
  typedef ULONG (NTAPI* RtlNtStatusToDosErrorPtr)(NTSTATUS Status);

  // Locating functions
  HINSTANCE hNtDll = GetModuleHandleW(L"ntdll.dll");
  NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hNtDll, "NtQueryInformationProcess");
  RtlNtStatusToDosErrorPtr RtlNtStatusToDosError = (RtlNtStatusToDosErrorPtr)GetProcAddress(hNtDll, "RtlNtStatusToDosError");

  if(!NtQueryInformationProcess || !RtlNtStatusToDosError)
  {
    printf("Functions cannot be located.\n");
    return 0;
  }
  
  // Get PROCESS_BASIC_INFORMATION
  PROCESS_BASIC_INFORMATION pbi;
  ULONG len;
  NTSTATUS status = NtQueryInformationProcess(
    hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &len);
  SetLastError(RtlNtStatusToDosError(status));
  if(NT_ERROR(status) || !pbi.PebBaseAddress)
  {
    printf("NtQueryInformationProcess(ProcessBasicInformation) failed.\n");
    return 0;
  }

  // Read PEB memory block
  SIZE_T bytesRead = 0;
  PEB_INTERNAL peb;
  if(!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), &bytesRead))
  {
    printf("Reading PEB failed.\n");
    return 0;
  }
  
  // Obtain size of commandline string
  RTL_USER_PROCESS_PARAMETERS_I upp;
  if(!ReadProcessMemory(hProcess, peb.ProcessParameters, &upp, sizeof(upp), &bytesRead))
  {
    printf("Reading USER_PROCESS_PARAMETERS failed.\n");
    return 0;
  }
  
  if(!upp.CommandLine.Length)
  {
    printf("Command line length is 0.\n");
    return 0;
  }
  
  // Check the buffer size
  DWORD dwNeedLength = (upp.CommandLine.Length+1) / sizeof(wchar_t) +1;
  if(bufferLength < dwNeedLength)
  {
    printf("Not enough buffer.\n");
    return dwNeedLength;
  }
  
  // Get the actual command line
  pszBuffer[dwNeedLength - 1] = L'\0';
  if(!ReadProcessMemory(hProcess, upp.CommandLine.Buffer, pszBuffer, upp.CommandLine.Length, &bytesRead))
  {
    printf("Reading command line failed.\n");
    return 0;
  }
  
  return bytesRead / sizeof(wchar_t);
}

int wmain()
{
  setlocale(LC_ALL, "");
  
  HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if(hSnapshot == INVALID_HANDLE_VALUE)
    return 0;
  
  PROCESSENTRY32 pe;
  pe.dwSize = sizeof(pe);
  if(!Process32First(hSnapshot, &pe))
  {
    CloseHandle(hSnapshot);
    return 0;
  }
  do
  {
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ProcessID);
    if(hProcess)
    {
      WCHAR buf[MAX_PATH];
      buf[0] = 0;
      if(GetRemoteCommandLineW(hProcess, buf, MAX_PATH))
        wprintf(L"%08X: %s\n", pe.th32ProcessID, buf);
      else
        wprintf(L"%08X: ????\n", pe.th32ProcessID);
      CloseHandle(hProcess);
    }
  }
  while(Process32Next(hSnapshot, &pe));
  
  CloseHandle(hSnapshot);
}

このコードで現在起動している全プロセスのコマンドラインがとれる。