完成了定位和修改程序中调用API函数代码后,我们就可以随意设计自定义的API函数的替代函数了。做完这一切后,还需要将这些代码注入到被外挂游戏程序进程内存空间中,不然游戏进程根本不会访问到替代函数代码。注入方法有很多,如利用全局钩子注入、利用注册表注入挡截User32库中的API函数、利用CreateRemoteThread注入(仅限于NT/2000)、利用BHO注入等。因为我们在动作模拟技术一节已经接触过全局钩子,我相信聪明的读者已经完全掌握了全局钩子的制作过程,所以我们在后面的实例中,将继续利用这个全局钩子。至于其它几种注入方法,如果感兴趣可参阅MSDN有关内容。
有了以上理论基础,我们下面就开始制作一个挡截MessageBoxA和recv函数的实例,在开发游戏外挂程序 时,可以此实例为框架,加入相应的替代函数和处理代码即可。此实例的开发过程如下:
(1) 打开前面创建的ActiveKey项目。
(2) 在ActiveKey.h文件中加入HOOKAPI结构,此结构用来存储被挡截API函数名称、原API函数地址和替代函数地址。
typedef struct tag_HOOKAPI { LPCSTR szFunc;//被HOOK的API函数名称。 PROC pNewProc;//替代函数地址。 PROC pOldProc;//原API函数地址。 }HOOKAPI, *LPHOOKAPI; |
(3) 打开ActiveKey.cpp文件,首先加入一个函数,用于定位输入库在输入数据段中的IAT地址。代码如下:
extern "C" __declspec(dllexport)PIMAGE_IMPORT_DESCRIPTOR LocationIAT(HMODULE hModule, LPCSTR szImportMod) //其中,hModule为进程模块句柄;szImportMod为输入库名称。 { //检查是否为DOS程序,如是返回NULL,因DOS程序没有IAT。 PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule; if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL; //检查是否为NT标志,否则返回NULL。 PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader->e_lfanew)); if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return NULL; //没有IAT表则返回NULL。 if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0) return NULL; //定位第一个IAT位置。 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)); //根据输入库名称循环检查所有的IAT,如匹配则返回该IAT地址,否则检测下一个IAT。 while (pImportDesc->Name) { //获取该IAT描述的输入库名称。 PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc->Name)); if (stricmp(szCurrMod, szImportMod) == 0) break; pImportDesc++; } if(pImportDesc->Name == NULL) return NULL; return pImportDesc; } |
再加入一个函数,用来定位被挡截API函数的IAT项并修改其内容为替代函数地址。代码如下:
extern "C" __declspec(dllexport) HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi) //其中,hModule为进程模块句柄;szImportMod为输入库名称;pHookAPI为HOOKAPI结构指针。 { //定位szImportMod输入库在输入数据段中的IAT地址。 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod); if (pImportDesc == NULL) return FALSE; //第一个Thunk地址。 PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk)); //第一个IAT项的Thunk地址。 PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk)); //循环查找被截API函数的IAT项,并使用替代函数地址修改其值。 while(pOrigThunk->u1.Function) { //检测此Thunk是否为IAT项。 if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG) { //获取此IAT项所描述的函数名称。 PIMAGE_IMPORT_BY_NAME pByName =(PIMAGE_IMPORT_BY_NAME)((DWORD)hModule+(DWORD)(pOrigThunk->u1.AddressOfData)); if(pByName->Name[0] == '/0') return FALSE; //检测是否为挡截函数。 if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0) { MEMORY_BASIC_INFORMATION mbi_thunk; //查询修改页的信息。 VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); //改变修改页保护属性为PAGE_READWRITE。 VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect); //保存原来的API函数地址。 if(pHookApi->pOldProc == NULL) pHookApi->pOldProc = (PROC)pRealThunk->u1.Function; //修改API函数IAT项内容为替代函数地址。 pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc; //恢复修改页保护属性。 DWORD dwOldProtect; VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect); } } pOrigThunk++; pRealThunk++; } SetLastError(ERROR_SUCCESS); //设置错误为ERROR_SUCCESS,表示成功。 return TRUE; } |
(4) 定义替代函数,此实例中只给MessageBoxA和recv两个API进行挡截。代码如下:
static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) { //过滤掉原MessageBoxA的正文和标题内容,只显示如下内容。 return MessageBox(hWnd, "Hook API OK!", "Hook API", uType); } static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags ) { //此处可以挡截游戏服务器发送来的网络数据包,可以加入分析和处理数据代码。 return recv(s,buf,len,flags); } |
(5) 在KeyboardProc函数中加入激活挡截API代码,在if( wParam == 0X79 )语句中后面加入如下else if语句:
...... //当激活F11键时,启动挡截API函数功能。 else if( wParam == 0x7A ) { HOOKAPI api[2]; api[0].szFunc ="MessageBoxA";//设置被挡截函数的名称。 api[0].pNewProc = (PROC)MessageBoxA1;//设置替代函数的地址。 api[1].szFunc ="recv";//设置被挡截函数的名称。 api[1].pNewProc = (PROC)recv1; //设置替代函数的地址。 //设置挡截User32.dll库中的MessageBoxA函数。 HookAPIByName(GetModuleHandle(NULL),"User32.dll",&api[0]); //设置挡截Wsock32.dll库中的recv函数。 HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&api[1]); } ...... |
(6) 在ActiveKey.cpp中加入头文件声明 "#include "wsock32.h"。 从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标签,在“对象/库模块”中输入Ws2_32..lib。
(7) 重新编译ActiveKey项目,产生ActiveKey.dll文件,将其拷贝到Simulate.exe目录下。运行Simulate.exe并启动全局钩子。激活任意应用程序,按F11键后,运行此程序中可能调用MessageBoxA函数的操作,看看信息框是不是有所变化。同样,如此程序正在接收网络数据包,就可以实现封包功能了。
六、结束语
除了以上介绍的几种游戏外挂程序常用的技术以外,在一些外挂程序中还使用了游戏数据修改技术、游戏加速技术等。在这篇文章里,就不逐一介绍了。