注意事项:
1.Debug版本编译的时候使用增量编译,导致每个函数都是用一个Thunk, 所以请使用Release版本。
2.目标进程非本进程时不能调用本进程内的函数或使用本进程内的变量,有时在隐式使用时可能会引起该
问题,容易引起进程崩溃。(例如WriteProcessMemory写入的函数中调用了本进程的全局变量)
3.多参数使用时请在目标进程中为函数参数分配相应的内存空间,因为CreateRemoteThread第5个参数是LPVOID型,
这意味着它只能放一个指针值,而该指针值应该指向分配的相应内存空间。
使用实例:
假设我们调用的目标进程的主窗口标题为“ImageCall”, 并假设在偏移该进程首地址0x000163D0有一个用于加血的游戏函数。
于是我们可以如下所示来使用CreateRemoteThread多参数调用来在目标进程中调用该加血函数。
(以下使用默认在MFC中,CImageBloodDlg为一个基本对话框类,
AddBlood_Inject()为对话框上某一按钮按下时的触发函数。)
//首先定义全局标题和偏移地址
LPCTSTR gameCaption = _T("ImageCall"); const int ADD_BLOOD_CALL = 0x000163D0;
//之后是上一篇博客中写道的获取进程首地址的函数
//获取目标进程首地址 BOOL CImageBloodDlg::getProcessAddr(DWORD dwPID, DWORD& baseAddr) { HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32; // 在目标进程中获取所有进程的snapshot hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID); if (hModuleSnap == INVALID_HANDLE_VALUE) { AfxMessageBox(_T("CreateToolhelp32Snapshot (of modules) fail")); return(FALSE); } // 设置MODULEENTRY32数据结构大小字段 me32.dwSize = sizeof(MODULEENTRY32); //检索第一个模块的信息,不成功则返回 if (!Module32First(hModuleSnap, &me32)) { AfxMessageBox(_T("Module32First fail")); // 显示调用失败 CloseHandle(hModuleSnap); // 清除句柄对象 return(FALSE); } // 从me32中得到基址 baseAddr = (DWORD)me32.modBaseAddr; // 别忘了最后清除模块句柄对象 CloseHandle(hModuleSnap); return(TRUE); }
//然后我们定义一个获取目标地址空间真实地址的结构体
typedef struct tagTrueAddr { DWORD baseAddr; DWORD Offset; }TrueAddr, *PTrueAddr;
//这是我们想在目标进程中进行调用的函数(使用了内联汇编)
void __stdcall addBloodCall(TrueAddr* addr) { DWORD baseAddr = addr->baseAddr; DWORD baseOffset = addr->Offset; _asm { mov eax, baseAddr add eax, baseOffset call eax } }
//最后我们通过远程注入来进行CreateRemoteThread的多参数调用,按下相应按钮时触发该函数
void CImageBloodDlg::AddBlood_Inject() { // TODO: 在此添加控件通知处理程序代码 //得到窗口句柄 HWND hwnd = ::FindWindow(NULL, gameCaption); //得到进程ID DWORD pid; GetWindowThreadProcessId(hwnd, &pid); //获取进程访问权限 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid); //目标进程分配函数参数空间 LPVOID paramsCall = VirtualAllocEx(hProcess, NULL, sizeof(TrueAddr), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (paramsCall == NULL) { AfxMessageBox(_T("get paramsCall failed")); return; } //目标进程分配函数本体空间 LPVOID baseCall = VirtualAllocEx(hProcess, NULL, 1000, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (baseCall == NULL) { AfxMessageBox(_T("get baseCall failed")); return; } //在函数本体空间中写入addBloodCall函数本体 if (!WriteProcessMemory(hProcess, baseCall, addBloodCall, 1000, NULL)) { AfxMessageBox(_T("writeProcessMemory fail")); return; } //得到目标进程首地址 DWORD baseAddr; getProcessAddr(pid, baseAddr); //设置真实地址(baseAddr首地址, Offset偏移地址量) TrueAddr trueAddr; trueAddr.baseAddr = baseAddr; trueAddr.Offset = ADD_BLOOD_CALL; if (!WriteProcessMemory(hProcess, paramsCall, (LPCVOID)&trueAddr, sizeof(TrueAddr), NULL)) { AfxMessageBox(_T("Write trueAddress failed")); return; } //传入函数参数并创建远程线程函数 PTrueAddr ptAddr = (PTrueAddr)paramsCall; HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)baseCall, (LPVOID)ptAddr, 0,NULL); if (!hRemoteThread) { AfxMessageBox(_T("CreateRemoteThread failed")); return; } }
先定义一个结构体的数据结构类型,并在该结构中包括所有的函数入口参数,
然后为想写入的函数提供唯一的一个结构体指针的参数,最后在CreateRemoteThread中
提供该唯一的函数参数,并为该函数参数指针所指的结构体在目标进程中开辟相应的内存空间,
并使该指针指向它,通过这种方法我们便可以成功使用多参数函数传入的CreateRemoteThread调用了。