注意事项:
1.Debug版本号编译的时候使用增量编译,导致每一个函数都是用一个Thunk, 所以请使用Release版本号。
2.目标进程非本进程时不能调用本进程内的函数或使用本进程内的变量,有时在隐式使用时可能会引起该
问题,easy引起进程崩溃。(比如WriteProcessMemory写入的函数中调用了本进程的全局变量)
3.多參数使用时请在目标进程中为函数參数分配对应的内存空间。由于CreateRemoteThread第5个參数是LPVOID型,
这意味着它仅仅能放一个指针值,而该指针值应该指向分配的对应内存空间。
使用实例:
如果我们调用的目标进程的主窗体标题为“ImageCall”, 并如果在偏移该进程首地址0x000163D0有一个用于加血的游戏函数。
于是我们能够例如以下所看到的来使用CreateRemoteThread多參数调用来在目标进程中调用该加血函数。
(下面使用默认在MFC中,CImageBloodDlg为一个基本对话框类,
AddBlood_Inject()为对话框上某一button按下时的触发函数。)
//首先定义全局标题和偏移地址
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的多參数调用,按下对应button时触发该函数
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调用了。