他プロセスのメモリを読む(C++編)
メモリの読み込み自体はWin32APIのReadProcessMemoryを使えば良い。
ただし、WindowsVista以降ではASLR(Address space layout randomization)機能により、実行ファイルなどの配置アドレスがランダムになっている。そのため、まずはその実行ファイルのメインモジュールの先頭アドレスを取得し、そこからの相対位置指定でメモリを読むことにする。
メインモジュールの先頭アドレスはPEB(Process Enviroment Block)内のImageBaseAddressの値を見れば良い。
(その他にGetModuleHandler(NULL)でも取得できるらしい)
ターゲットプロセスのハンドルを取得
| /* ターゲットプロセスのハンドルを取得 */ | |
| HANDLE get_process_hundle(TCHAR* target_exe_file) { | |
| // 起動している全プロセスの情報を取得 | |
| HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | |
| if(hSnapshot == INVALID_HANDLE_VALUE) { | |
| printf("Error: hSnapshot is INVALID_HANDLE_VALUE\n"); | |
| return INVALID_HANDLE_VALUE; | |
| } | |
| PROCESSENTRY32 pe; | |
| pe.dwSize = sizeof(PROCESSENTRY32); | |
| // プロセスの取得 (初回のみProcess32First) | |
| if(!Process32First(hSnapshot, &pe)) { | |
| CloseHandle(hSnapshot); | |
| printf("Error: couldn't get entry by Process32First\n"); | |
| return INVALID_HANDLE_VALUE; | |
| } | |
| do { | |
| // 実行ファイル名で比較 | |
| if(_tcscmp(pe.szExeFile, target_exe_file) == 0) { | |
| // 対象のプロセスを発見 | |
| CloseHandle(hSnapshot); | |
| HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ProcessID); | |
| if(hProcess) { | |
| return hProcess; | |
| } else { | |
| return INVALID_HANDLE_VALUE; | |
| } | |
| } | |
| } while(Process32Next(hSnapshot, &pe)); | |
| // ハンドルを閉じる | |
| CloseHandle(hSnapshot); | |
| return INVALID_HANDLE_VALUE; | |
| } |
プロセスのベースアドレスを取得
プロセスハンドルからNtQueryInformationProcessを使ってPROCESS_BASIC_INFORMATIONを取得する。
その中にPBEのベースアドレスがあるので、それを利用してReadProcessMemoryを行う
PBE構造体
既存の定義ではImageBaseAddressが載っていないので、以下の構造体定義を行う。
| struct PEB_INTERNAL { | |
| BYTE InheritedAddressSpace; | |
| BYTE ReadImageFileExecOptions; | |
| BYTE BeingDebugged; | |
| BYTE Spare; | |
| PVOID Mutant; | |
| PVOID ImageBaseAddress; | |
| 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; | |
| }; |
ベースアドレス取得
| /* プロセスのベースアドレスを取得 */ | |
| PVOID get_process_base_address(HANDLE hProcess) { | |
| // ntdllのインスタンスを取得 | |
| HINSTANCE hNtDll = GetModuleHandleW(L"ntdll.dll"); | |
| // ntdllのモジュールを配置 | |
| NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hNtDll, "NtQueryInformationProcess"); | |
| RtlNtStatusToDosErrorPtr RtlNtStatusToDosError = (RtlNtStatusToDosErrorPtr)GetProcAddress(hNtDll, "RtlNtStatusToDosError"); | |
| if(!NtQueryInformationProcess || !RtlNtStatusToDosError) { | |
| printf("Functions cannot be located.\n"); | |
| return NULL; | |
| } | |
| // 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 NULL; | |
| } | |
| // PBE(Process Environment Block)の読み込み | |
| SIZE_T bytesRead = 0; | |
| PEB_INTERNAL peb; | |
| if(!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), &bytesRead)) | |
| { | |
| printf("Reading PEB failed.\n"); | |
| return NULL; | |
| } | |
| return peb.ImageBaseAddress; | |
| } |
実際にメモリ内容を取得してみる
| int main(void) { | |
| HANDLE hProcess = get_process_hundle(L"test.exe"); | |
| if(hProcess == INVALID_HANDLE_VALUE) { | |
| exit(1); | |
| } | |
| PVOID pBaseAddress = get_process_base_address(hProcess); | |
| if(pBaseAddress == NULL) { | |
| exit(1); | |
| } | |
| // 整数値に変換 | |
| uint64_t iBaseAddress = reinterpret_cast<uintptr_t>(pBaseAddress); | |
| // テスト読み込み | |
| int data = 0; | |
| SIZE_T bytesRead = 0; | |
| ReadProcessMemory(hProcess, (PVOID)(iBaseAddress + 0x109A6E0), &data, 4, &bytesRead); | |
| printf("data is %d\n", data); | |
| } |