听dj战歌,就上傲气战歌网!2015年传奇家族玩家最喜爱的家族战歌网
战歌推荐:战歌网 战歌网dj Mc战歌网 DJ战歌网下载 激情战歌-冰雪战歌网 客服Q:350317
新闻搜索:

游戏外挂探秘(三)

作者:     来源:    发表时间:2011-11-26 14:05


五、封包技术  通过对动作模拟技术的介绍,我们对游戏外挂有了一定程度上的认识,也学会了使用动作模拟技术来实现简单的动作模拟型游戏外挂的制作。这种动作模拟型游戏外挂有一定的局限性,它仅仅只能解决使用计算机代替人力完成那么有规律、繁琐而无聊的游戏动作。但是,随着网络游戏的盛行和复杂度的增加,很多游戏要求将客户端动作信息及时反馈回服务器,通过服务器对这些动作信息进行有效认证后,再向客户端发送下一步游戏动作信息,这样动作模拟技术将失去原有的效应。为了更好地“外挂”这些游戏,游戏外挂程序也进行了升级换代,它们将以前针对游戏用户界面层的模拟推进到数据通讯层,通过封包技术在客户端挡截游戏服务器发送来的游戏控制数据包,分析数据包并修改数据包;同时还需按照游戏数据包结构创建数据包,再模拟客户端发送给游戏服务器,这个过程其实就是一个封包的过程。  封包的技术是实现第二类游戏外挂的最核心的技术。封包技术涉及的知识很广泛,实现方法也很多,如挡截WinSock、挡截API函数、挡截消息、VxD驱动程序等。在此我们也不可能在此文中将所有的封包技术都进行详细介绍,故选择两种在游戏外挂程序中最常用的两种方法:挡截WinSock和挡截API函数。  1. 挡截WinSock  众所周知,Winsock是Windows网络编程接口,它工作于Windows应用层,它提供与底层传输协议无关的高层数据传输编程接口。在Windows系统中,使用WinSock接口为应用程序提供基于TCP/IP协议的网络访问服务,这些服务是由Wsock32.DLL动态链接库提供的函数库来完成的。  由上说明可知,任何Windows基于TCP/IP的应用程序都必须通过WinSock接口访问网络,当然网络游戏程序也不例外。由此我们可以想象一下,如果我们可以控制WinSock接口的话,那么控制游戏客户端程序与服务器之间的数据包也将易如反掌。按着这个思路,下面的工作就是如何完成控制WinSock接口了。由上面的介绍可知,WinSock接口其实是由一个动态链接库提供的一系列函数,由这些函数实现对网络的访问。有了这层的认识,问题就好办多了,我们可以制作一个类似的动态链接库来代替原WinSock接口库,在其中实现WinSock32.dll中实现的所有函数,并保证所有函数的参数个数和顺序、返回值类型都应与原库相同。在这个自制作的动态库中,可以对我们感兴趣的函数(如发送、接收等函数)进行挡截,放入外挂控制代码,最后还继续调用原WinSock库中提供的相应功能函数,这样就可以实现对网络数据包的挡截、修改和发送等封包功能。  下面重点介绍创建挡截WinSock外挂程序的基本步骤:  (1) 创建DLL项目,选择Win32 Dynamic-Link Library,再选择An empty DLL project。  (2) 新建文件wsock32.h,按如下步骤输入代码:  ① 加入相关变量声明:   HMODULE hModule=NULL; //模块句柄   char buffer[1000]; //缓冲区   FARPROC proc; //函数入口指针   ② 定义指向原WinSock库中的所有函数地址的指针变量,因WinSock库共提供70多个函数,限于篇幅,在此就只选择几个常用的函数列出,有关这些库函数的说明可参考MSDN相关内容。   //定义指向原WinSock库函数地址的指针变量。SOCKET (__stdcall *socket1)(int ,int,int);//创建Sock函数。   int (__stdcall *WSAStartup1)(WORD,LPWSADATA);//初始化WinSock库函数。   int (__stdcall *WSACleanup1)();//清除WinSock库函数。   int (__stdcall *recv1)(SOCKET ,char FAR * ,int ,int );//接收数据函数。   int (__stdcall *send1)(SOCKET ,const char * ,int ,int);//发送数据函数。   int (__stdcall *connect1)(SOCKET,const struct sockaddr *,int);//创建连接函数。   int (__stdcall *bind1)(SOCKET ,const struct sockaddr *,int );//绑定函数。   ......其它函数地址指针的定义略。   (3) 新建wsock32.cpp文件,按如下步骤输入代码:  ① 加入相关头文件声明:   #include    #include    #include "wsock32.h"   ② 添加DllMain函数,在此函数中首先需要加载原WinSock库,并获取此库中所有函数的地址。代码如下:   BOOL WINAPI DllMain (HANDLE hInst,ULONG ul_reason_for_call,LPVOID lpReserved)   {    if(hModule==NULL){     //加载原WinSock库,原WinSock库已复制为wsock32.001。   hModule=LoadLibrary("wsock32.001");   }    else return 1;//获取原WinSock库中的所有函数的地址并保存,下面仅列出部分代码。if(hModule!=NULL){     //获取原WinSock库初始化函数的地址,并保存到WSAStartup1中。proc=GetProcAddress(hModule,"WSAStartup");   WSAStartup1=(int (_stdcall *)(WORD,LPWSADATA))proc;     //获取原WinSock库消除函数的地址,并保存到WSACleanup1中。    proc=GetProcAddress(hModule i,"WSACleanup");    WSACleanup1=(int (_stdcall *)())proc;     //获取原创建Sock函数的地址,并保存到socket1中。    proc=GetProcAddress(hModule,"socket");     socket1=(SOCKET (_stdcall *)(int ,int,int))proc;     //获取原创建连接函数的地址,并保存到connect1中。     proc=GetProcAddress(hModule,"connect");     connect1=(int (_stdcall *)(SOCKET ,const struct sockaddr *,int ))proc;     //获取原发送函数的地址,并保存到send1中。     proc=GetProcAddress(hModule,"send");     send1=(int (_stdcall *)(SOCKET ,const char * ,int ,int ))proc;     //获取原接收函数的地址,并保存到recv1中。     proc=GetProcAddress(hModule,"recv");     recv1=(int (_stdcall *)(SOCKET ,char FAR * ,int ,int ))proc;     ......其它获取函数地址代码略。   }   else return 0;   return 1;}   ③ 定义库输出函数,在此可以对我们感兴趣的函数中添加外挂控制代码,在所有的输出函数的最后一步都调用原WinSock库的同名函数。部分输出函数定义代码如下://库输出函数定义。//WinSock初始化函数。    int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)    {     //调用原WinSock库初始化函数     return WSAStartup1(wVersionRequired,lpWSAData);    }    //WinSock结束清除函数。    int PASCAL FAR WSACleanup(void)    {     return WSACleanup1(); //调用原WinSock库结束清除函数。    }    //创建Socket函数。    SOCKET PASCAL FAR socket (int af, int type, int protocol)    {     //调用原WinSock库创建Socket函数。     return socket1(af,type,protocol);    }    //发送数据包函数    int PASCAL FAR send(SOCKET s,const char * buf,int len,int flags)    {   //在此可以对发送的缓冲buf的内容进行修改,以实现欺骗服务器。   外挂代码......   //调用原WinSock库发送数据包函数。     return send1(s,buf,len,flags);    }//接收数据包函数。    int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags)    {   //在此可以挡截到服务器端发送到客户端的数据包,先将其保存到buffer中。   strcpy(buffer,buf);   //对buffer数据包数据进行分析后,对其按照玩家的指令进行相关修改。   外挂代码......   //最后调用原WinSock中的接收数据包函数。     return recv1(s, buffer, len, flags);     }    .......其它函数定义代码略。(4)、新建wsock32.def配置文件,在其中加入所有库输出函数的声明,部分声明代码如下:   LIBRARY "wsock32"   EXPORTS     WSAStartup @1   WSACleanup @2    recv @3    send @4    socket @5   bind @6   closesocket @7   connect @8    ......其它输出函数声明代码略。  (5)、从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标签,在“对象/库模块”中输入Ws2_32.lib。  (6)、编译项目,产生wsock32.dll库文件。  (7)、将系统目录下原wsock32.dll库文件拷贝到被外挂程序的目录下,并将其改名为wsock.001;再将上面产生的wsock32.dll文件同样拷贝到被外挂程序的目录下。重新启动游戏程序,此时游戏程序将先加载我们自己制作的wsock32.dll文件,再通过该库文件间接调用原WinSock接口函数来实现访问网络。上面我们仅仅介绍了挡载WinSock的实现过程,至于如何加入外挂控制代码,还需要外挂开发人员对游戏数据包结构、内容、加密算法等方面的仔细分析(这个过程将是一个艰辛的过程),再生成外挂控制代码。关于数据包分析方法和技巧,不是本文讲解的范围,如您感兴趣可以到网上查查相关资料。(3)、注入外挂代码进入被挂游戏进程中  完成了定位和修改程序中调用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函数的操作,看看信息框是不是有所变化。同样,如此程序正在接收网络数据包,就可以实现封包功能了。  六、结束语  除了以上介绍的几种游戏外挂程序常用的技术以外,在一些外挂程序中还使用了游戏数据修改技术、游戏加速技术等。在这篇文章里,就不逐一介绍了。网络游戏外挂核心封包揭密

数据统计中!!

看过该文章的网友还看过
最新评论共有  位网友发表了评论
发表评论(评论内容:请文明参与评论,禁止谩骂攻击!)
不能超过250字节,请自觉遵守互联网相关政策法规.
昵称:    发表评论 (Ctrl+Enter快速回复)
推荐新闻

关于本站 | 合作加盟 | 合作说明 | 免责声明 | 广告服务 | 网站地图

健康游戏忠告:抵制不良游戏 拒绝盗版游戏 注意自我保护 谨防受骗上当 适度游戏益脑 沉迷游戏伤身 合理安排时间 享受健康生活

如有意见和建议,请惠赐E-mail至350317@qq.com 联系QQ:350317

Copyright © 2010-2013 Www.27zG.CoM
苏ICP备11049833号