qq游戏外挂:游戏外挂设计技术探讨③

2.挡截API

  挡截API技术和挡截WinSock技术在原理上很相似但是前者比后者提供了更强大功能挡截WinSock仅只能挡截WinSock接口而挡截API可以实现对应用包括WinSock API在内所有API挡截如果您外挂仅打算对WinSock进行挡截您可以只选择使用上小节介绍挡截WinSock技术随着大量外挂在功能上扩展它们不仅仅只提供对数据包挡截而且还对游戏中使用Windows API或其它DLL库挡截以使外挂功能更加强大例如可以通过挡截相关API以实现对非中文游戏汉化功能有了这个利器可以使您外挂无所不能了

  挡截API技术原理核心也是使用我们自己来替换掉Windows或其它DLL库提供有点同挡截WinSock原理相似吧但是其实现过程却比挡截WinSock要复杂如像实现挡截Winsock过程将应用所有库文件都写个模拟库有点不大可能就只说Windows API就有上千个还有很多库提供结构并未公开所以写个模拟库代替方式不大现实故我们必须另谋良方

  挡截API最终目标是使用自定义代替原那么我们首先应该知道应用何时、何地、用何种方式接下来需要将应用该原指令代码进行修改使它将指针指向我们自己定义地址这样外挂才能完全控制应用API至于在其中如何加入外挂代码就应需求而异了最后还有个重要问题要解决如何将我们自定义用来代替原API代码注入被外挂游戏进行地址空间中因在Windows系统中应用仅只能访问到本进程地址空间内代码和数据

  综上所述要实现挡截API至少需要解决如下 3个问题:

  ● 如何定位游戏API指令代码?

  ● 如何修改游戏API指令代码?

  ● 如何将外挂代码(自定义替换代码)注入到游戏进程地址空间?

  下面我们逐介绍这几个问题解决思路方法:

  (1) 、定位API指令代码

  我们知道在汇编语言中使用CALL指令来或过程它是通过指令参数中地址而定位到相应代码那么我们如果能寻找到代码中所有被挡截APICALL指令就可以将该指令中地址参数修改为替代地址虽然这是个可行方案但是实现起来会很繁琐也不稳健庆幸Windows系统中所使用可执行文件(PE格式)采用了输入地址表机制将所有在API地址信息存放在输入地址表中而在代码CALL指令中使用地址不是API地址而是输入地址表中该API地址项如想使代码中API被代替掉只用将输入地址表中该API地址项内容修改即可具体理解输入地址表运行机制还需要了解下PE格式文件结构其中图 3列出了PE格式文件大致结构


  图 3:PE格式大致结构图(003.jpg)

  PE格式文件开始是段DOS当你在不支持Windows环境中运行时它就会显示“This Program cannot be run in DOS mode”这样警告语句接着这个DOS文件头就开始真正PE文件内容了首先是段称为“IMAGE_NT_HEADER”数据其中是许多有关整个PE文件消息在这段数据尾端是个称为Data Directory数据表通过它能快速定位些PE文件中段(section)地址在这段数据的后则是个“IMAGE_SECTION_HEADER”列表其中项都详细描述了后面个段相关信息接着它就是PE文件中最主要段数据了执行代码、数据和资源等等信息就分别存放在这些段中

  在所有这些段里个被称为“.idata”段(输入数据段)值得我们去注意该段中包含着些被称为输入地址表(IATImport Address Table)数据列表每个用隐式方式加载API所在DLL都有个IAT和的对应同时个API地址也和IAT中项相对应个应用加载到内存中后针对每个API相应产生如下汇编指令:

  JMP DWORD PTR [XXXXXXXX]

  或

  CALL DWORD PTR [XXXXXXXX]

  其中[XXXXXXXX]表示指向了输入地址表中个项其内容是个DWORD而正是这个DWORD才是API在内存中真正地址因此我们要想拦截个API只要简单把那个DWORD改为我们自己地址

  (2) 、修改API代码

  从上面对PE文件格式分析可知修改API代码其实是修改被API在输入地址表中IAT项内容由于Windows系统对应用指令代码地址空间严密保护机制使得修改指令代码非常困难以至于许多高手为的编写VxD进入Ring0在这里我为大家介绍种较为方便思路方法修改进程内存它仅需要几个Windows核心API下面我首先来学会下这几个API:

   DWORD VirtualQuery(
   LPCVOID lpAddress, // address of region
   PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer
   DWORD dwLength // size of buffer
   );

  该用于查询有关本进程内虚拟地址页信息其中lpAddress表示被查询页区域地址;lpBuffer表示用于保存查询页信息缓冲;dwLength表示缓冲区大小返回值为实际缓冲大小

   BOOL VirtualProtect(
   LPVOID lpAddress, // region of committed pages
   SIZE_T dwSize, // size of the region
   DWORD flNewProtect, // desired access protection
   PDWORD lpflOldProtect // old protection
   );

  该用于改变本进程内虚拟地址页保护属性其中lpAddress表示被改变保护属性页区域地址;dwSize表示页区域大小;flNewProtect表示新保护属性可取值为PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE等;lpflOldProtect表示用于保存改变前保护属性如果成功返回“T”否则返回“F”

  有了这两个API我们就可以随心所欲修改进程内存了首先VirtualQuery查询被修改内存页信息再根据此信息VirtualProtect改变这些页保护属性为PAGE_READWRITE有了这个权限您就可以任意修改进程内存数据了下面段代码演示了如何将进程虚拟地址为0x0040106c处字节清零

   BYTE* pData = 0x0040106c;
   MEMORY_BASIC_INFORMATION mbi_thunk;
   //查询页信息
   VirtualQuery(pData, &mbi_thunk, (MEMORY_BASIC_INFORMATION));
   //改变页保护属性为读写
   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
   PAGE_READWRITE, &mbi_thunk.Protect);
   //清零
   *pData = 0x00;
   //恢复页原保护属性
   DWORD dwOldProtect;
   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
   mbi_thunk.Protect, &dwOldProtect);  
  (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;//被HOOKAPI名称
   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;
   (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) NULL;
    //检查是否为NT标志否则返回NULL
    PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader->e_lfa));
    (pNTHeader->Signature != IMAGE_NT_SIGNATURE) NULL;
    //没有IAT表则返回NULL
    (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress 0) 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));
    (stricmp(szCurrMod, szImportMod) 0) ;
   pImportDesc;
    }
    (pImportDesc->Name NULL) NULL;
    pImportDesc;
   }

  再加入用来定位被挡截APIIAT项并修改其内容为替代地址代码如下:

   extern "C" __declspec(dllexport)
   HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi)
   //其中hModule为进程模块句柄;szImportMod为输入库名称;pHookAPI为HOOKAPI结构指针
   {
    //定位szImportMod输入库在输入数据段中IAT地址
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod);
   (pImportDesc NULL) 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));
    //循环查找被截APIIAT项并使用替代地址修改其值
   while(pOrigThunk->u1.Function)
{
 //检测此Thunk是否为IAT项
((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));
 (pByName->Name[0] '\0') FALSE;
  //检测是否为挡截
(strcmpi(pHookApi->szFunc, (char*)pByName->Name) 0)
  {
       MEMORY_BASIC_INFORMATION mbi_thunk;
       //查询修改页信息
       VirtualQuery(pRealThunk, &mbi_thunk, (MEMORY_BASIC_INFORMATION));
//改变修改页保护属性为PAGE_READWRITE
       VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
//保存原来API地址
      (pHookApi->pOldProc NULL)
pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
  //修改APIIAT项内容为替代地址
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表示成功
   TRUE;
   }  

  (4) 定义替代此例子中只给MessageBoxA和recv两个API进行挡截代码如下:

    WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
   {
    //过滤掉原MessageBoxA正文和标题内容只显示如下内容
MessageBox(hWnd, "Hook API OK!", "Hook API", uType);
   }
    WINAPI recv1(SOCKET s, char FAR *buf, len, flags )
   {
   //此处可以挡截游戏服务器发送来网络数据包可以加入分析和处理数据代码
    recv(s,buf,len,flags);
   }  

  (5) 在KeyboardProc中加入激活挡截API代码( wParam 0X79 )语句中后面加入如下 语句:

   ......
   //当激活F11键时启动挡截API功能
    ( 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中加入头文件声明 "# "wsock32.h" 从“工程”菜单中选择“设置”弹出Project Setting对话框选择Link标签在“对象/库模块”中输入Ws2_32..lib

  (7) 重新编译ActiveKey项目产生ActiveKey.dll文件将其拷贝到Simulate.exe目录下运行Simulate.exe并启动全局钩子激活任意应用按F11键后运行此中可能MessageBoxA操作看看信息框是不是有所变化同样如此正在接收网络数据包就可以实现封包功能了

   6、结束语

  除了以上介绍几种游戏外挂常用技术以外些外挂中还使用了游戏数据修改技术、游戏加速技术等在这篇文章里就不逐介绍了



  • 篇文章: 游戏外挂设计技术探讨②

  • 篇文章: MS04-011远程缓冲区溢出代码
  • Tags:  qq空间游戏外挂 qq空间游戏基地外挂 游戏外挂 qq游戏外挂

    延伸阅读

    最新评论

    发表评论