他プロセスのメモリを読む(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;
};
view raw read_memory_pbe.cpp hosted with ❤ by GitHub

ベースアドレス取得

/* プロセスのベースアドレスを取得 */
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);
}
view raw read_memory_main.cpp hosted with ❤ by GitHub