发新话题
打印

协议型网络游戏外挂制作之启动

协议型网络游戏外挂制作之启动

目前,网络游戏的外挂从程序角度主要分为辅助型的动作外挂和内核型的协议型外挂。动作外挂主要帮助玩家进行一些重复性的劳动量,网络上有许多介绍这方面的程序,按键精灵就是一个很好的例子。协议型外挂则给人一个很神秘的感觉,这方面在网络游戏上最多的恐怕是传奇的脱机外挂,这也是因为传奇最受欢迎而已。  
  在具体的讲解前,我很想扯点别的事情,毕竟学院不是我的风格,我更喜欢自由一点的。既然我们主要是针对协议型外挂的制作教程,就先扯扯网络游戏的流程和一些乱其八糟的事情,如果有冒犯你们的地方本人则不胜感激。如果你感到烦的话可以直接跳到你感兴趣的地方。
  我玩网游的历史很短,刚开始玩得是魔力宝贝,总是在免费的时候玩,在一个服务器里面呆的最长的就是在四川卧龙了,现在服务器大概早就该名或者并组了吧,在里面我的名字叫Bluerose,名字前面带的是我的职业,小号一大堆,不过名字都是一样的。用这个名字是因为朋友说我很忧郁,但却总抱着希望,所以就用这个名字了。后来玩的是大话西游II,是朋友拉着玩的,现在虽然上了班,但偶尔还玩,里面的名字叫星泪,用的是女性玩家,因为这个名字太女性化了。在刚开始玩得时候,我觉得自己好孤独(那时候在大学虽然旁边都是同学朋友,但内心仍然孤独),朋友拉我玩大话的时候,那天晚上星光倒不错,洒下的星光让我想起了眼泪的感觉,所以就叫独孤星泪了,不过感觉这个名字太裸露,就将独孤二字去掉了。
  由于玩大话西游II的历史比较长,而且对大话西游的游戏也比较熟悉,所以这次的教程就用大话西游II做为目标了,但教程尽可能考虑通用性。不过提前声明,本人对网络游戏并不熟悉,因此请勿和本人谈论网络游戏的前因后果和发展以及网游外挂对网络游戏的冲击等乱其八糟的事情。我对网络编程也是一知半解,因此我尽可能的避免含有网络的代码,同时,由于协议型的外挂需要发送数据,但可以通过其它的办法来进行(比如通过网游客户端来代理)。
  先说说服务器和客户端的通讯,由于服务器和本地的客户端不在同一个地理位置,相距比较遥远,因此数据的传输就需要一定的时间,这就决定了在网游中数据只能进行采样的收集处理而不能进行真正实时的数据处理。举例来说,局域网C-S(反恐精英)的数据处理,当你移动的时候,必须告知别的电脑玩家自己的移动,这个数据的传输由于在局域网内部,数据的传输比较快,量也比较小,电脑可以进行快速的采样和数据处理来进行判断是否打中或者移动是否违反规则(比如凌空徐步)等等,但在网络上进行玩的时候,对数据的采样就不像本地局域网那么快了,以大话西游II(以后简称大话吧,少打几个字)为例,在移动的时候,并不是将每一步的数据一个一个传送给服务器,而是将本次移动规则打成数据包提交给服务器,让后客户端开始播放动画,当服务器处理完数据之后,就会将位置回传给客户端,客户端以这个位置数据为基点,进行人物的下次移动,这个数据的采集需要隔一段时间来能进行一次采集,相对于CS来说,这个采集密度要比CS采集密度小。
  当服务器和客户端进行通讯的时候,数据包是至关重要的。数据包中数据的规则则是协议型外挂最重要的基础之一。由于网络数据可以进行拦截,为了防止数据被修改,数据包中的数据都是加密进行,至于如何加密,这由服务器和客户端通过一定的算法来执行。因此,服务器和客户端的通讯大概就是下面这个样子:
  客户端进行数据的采集===〉数据打包==〉数据加密==〉发送数据到服务器==〉服务器进行数据解密====〉服务器处理数据包==(处理完毕回传数据)==〉回传数据打包==〉回传数据加密==〉数据回传==〉客户端接收数据==〉客户端解密数据==〉客户端数据处理
  我们的目标就是数据包,即拦截游戏通讯间的数据来进行相应的修改或者进行发送伪数据包。我大概定了一个计划,不过这一节肯定不能全部讲完,我只能在下班之后写上一点,时间有限,能写多少就写多少吧。
  目标程序:大话西游II客户端。(你手上有服务器端吗?有的话我也想要)
  目标:数据包
  目的:数据包拦截,修改,伪发送
  编程软件:这个无所谓吧,不过我这里用的是C++ Builder 6,前段时间做CB 6相关的项目,而且CB
对于程序界面的编写是最方便不过的了,就是编译的有点慢和生成的程序有点大。(旁白:又不是做手机项目,担心容量吗?)我做受限程序做惯了,养成了不良习惯,没办法了。
  思路:我们的程序要干扰别的程序的运行,最好的办法是使用debug的办法,不过,我并没有打算使用debug的办法,我对程序的debug并不太熟悉,而且讨厌编写没用的代码。我准备采用线程注入的办法,至于线程注入,和为什么要线程注入才能干预,这方面的知识最好自己看看《Windows
核心编程》里面讲的,否则这个教程要没完没了了。当我们的线程注入到目标程序之后就方便多了,就可以为所欲为了。因此我们的第一目的是将线程注入到目标程序中。
  预备活动:
  线程注入最简单的莫过于hook了,如果连这都不知道的话,最好赶快到网上查查或者翻翻《Windows核心编程》。为了防止游戏内部存在反hook的存在和外挂的检测,我将用自己的程序来启动目标程序。由于网游的不定期更新,因此在启动程序的时候最好将升级跳过去,至少在大话这样的程序中我是这样做的,因为频繁升级和版本检测总让我等的时间太长。
  下面来进行具体的做法,我尽可能的弄出详细的步骤,如果你用的是VC或者其它的话,只要注意核心的代码就可以了。
  新建一个工程,在窗体上添加两个按钮(TButton或者其它类型的按钮),一个将标题改为启动游戏,另一个标题改为启动外挂。再添加一个TOpenDialog。对于默认的窗体那么大的界面有点浪费,因此将窗体弄得小点,别大大的怪吓人的。
  双点启动游戏的按钮就可以进行编写该按钮的事件了,默认的是OnClick事件。下面就是事件的代码:
if(FileExists(ExtractFileDir(Application->ExeName)+"\\path.ini")==FALSE)
{ /*我将目标程序的路径保存到了当前程序目录中的path.ini文件中,但如果当前程序第一次运行的话,
是不存在这个文件的,所以就可以用TOpdnDialog来打开了,
做这点只是为了方便,不用每次都得点目标程序*/
if(OpenFile->Execute())
{
 AnsiString
AppPath="path="+ExtractFilePath(OpenFile->FileName);
 WritePrivateProfileSection("XY2PATH",AppPath.c_str(),(ExtractFileDir(Application->ExeName)+
"\\path.ini").c_str());//将目标程序的路径存到path.ini文件中。
}else
{
 return;
}
}
//下面的代码开始启动目标程序
PROCESS_INFORMATION pi;
STARTUPINFO si;
si.cb=sizeof(si);
si.lpReserved=NULL;
si.lpDesktop=NULL;
si.lpTitle=NULL;
si.cbReserved2=0;
si.lpReserved2=NULL;
si.dwFlags=STARTF_USEPOSITION;
si.dwX=0;
si.dwY=0;
char
Appname[300];
GetPrivateProfileString("XY2PATH","path","",Appname,250,(ExtractFileDir(Application->ExeName)+
"\\path.ini").c_str());
strcat(Appname,"\\xy2.exe");  
/*以上都在构建目标程序的环境设置,下面调用CreateProcess来启动目标程序,
注意将倒数第3个参数要填为目标程序的路径,
第6个参数为Create_SUSPENDED是为了将程序加载到内存中之后可以进行一些修改,
以更好的配合外挂程序的运行*/
if(CreateProcess(Appname,NULL,NULL,NULL,FALSE,Create_SUSPENDED,NULL,ExtractFileDir(Appname).
c_str(),&si,&pi)==0)
{
 //启动目标程序失败
 showMessage("error
open exe file");
 return;
}
gamehandle=pi.hProcess;
/*在本节中要执行程序的话,最好将这个条件注释掉,我将在以后的教程中进行讲解,这里大概说一下功能,
第一个Write是为了跳过Update,第二个是为了退出的时候不打开网页,
我的电脑要是退出大话的时候打开网页的话,中间的时间可以抽上几根烟了,所以将程序改了*/
if(WriteProcessMemory(gamehandle,(void*)0x0042BC13,No_Update,1,NULL)==false
||WriteProcessMemory(gamehandle,(void*)0x00430a80,No_HTML,2,NULL)==false
)
return;
threadhand=pi.hThread;
gamethreadid=pi.dwThreadId;
//恢复程序,让程序执行
ResumeThread(pi.hThread);
/*下面的代码也是本节中不需要的,我将物品的有关信息存到了当前目录(外挂启动程序目录)中的item.ini文件中,
但目标程序中并不知道外挂启动程序的路径,因此我在目标程序文件夹中建立了一个名字叫path.ini文件,里面包含了item.ini的路径*/
String inipath= "path="+ExtractFileDir(Application->ExeName)+"\\item.ini";
WritePrivateProfileSection("ITEM",inipath.c_str(),(ExtractFileDir(OpenFile->FileName)+
"\\path.ini").c_str());
  
  启动程序中将启动属性设置为Create_SUSPENDED属性是为了考虑到程序的通用性和稳定性,在该函数之后,如果目标程序中存在有必要修改的代码的话,可以在这里进行修改,也可以对目标程序进行反反外挂的处理。其实,debug形式的外挂就可以在这里进行debug环境的建立,以及在目标程序中插入Int
3指令来进行拦截处理了(我怎么越来越感觉到自己在写调试器的教程??)。
  这节就讲到这里,如果再晚的话,我就没办法赶上公交车了,然后还得走回家,天哪,这么冷的天~~~~赶紧上传回家吧。
妖城欢迎您!

TOP

协议型网络游戏外挂制作之外挂窗口

上一次我们说了目标程序的启动,以及对目标程序的预处理。这一节中争取可以将外挂的窗口显出来,具体能不能说这么多,只能看着办了。
  因为我决定采用最俗的办法Hook来注入线程(有时候我都觉得自己是否有必要这么做,因为Debug的办法也不错),为了程序的更普遍性和更快的移植,以及简单一点,我决定还是采用Hook。这里提前说一下,如果不懂汇编和程序调试的话,最好先补一下课,这在以后要用的。
  我们先来编写Hook.dll部分,程序的启动部分暂时不用理会了(我以后就将那部分叫做wg.exe吧),昨天着急忘了说一声了,我让屏蔽的WriteProcessMemory中的数据地址是大话9.16更新之前的最后一个版本,在9.16更新之后的版本中需要先将程序脱壳,这部分我在以后会说的,所以让大家屏蔽掉那两个写内存的操作。
对于Hook.dll来说,我们准备使用F12键来激活外挂,在CB中编写dll非常简单,建立一个dll项目工程,然后就可以添加代码了。建立工程的时候一点记得选上使用C++,使用VCL,Multi
Thread这几个选项,理由:
  1、使用C++是为了让我省点口水(我将APIHOOK封装到了一个类里面)。
  2、使用VCL是因为我太懒惰,不想编写界面代码。
  3、使用多线程是因为程序必须。
  由于外挂主窗体在dll中,因此生成的dll就会比一般的dll大。窗体其实可以放到任何地方的,只是放到dll中比较方便而以,而且在说的时候可以更好的分开。
  以下是Hook.cpp的代码:
//---------------------------------------------------------------------------  
#include  
#include  
#include
"hookapi.h"
#include "hookform.h"
#pragma hdrstop
#pragma argsused
HHOOK g_hHook = NULL;//Hook的句柄
HINSTANCE DllHinst =
NULL; //Dll的句柄
HWND gamehWnd; //游戏句柄
HANDLE hThread = NULL; //线程句柄
HWND wghandle = NULL; //外挂窗口句柄
HANDLE gamehandle; //游戏窗口句柄,忘了有没有用
HINSTANCE gameInstance; //游戏的,也不知道用了没用
DWORD ThreadID; //线程ID
LRESULT CALLBACK KeyBoardHook(int nCode, WPARAM wParam, LPARAM
lParam);//键盘Hook
extern "C" __declspec(dllexport)bool EnableHook(DWORD
dwThreadId);//启动Hook的函数
extern "C" __declspec(dllexport)bool
DisableHook();//卸载Hook的函数,和上面的函数一样都是为了外部可以控制
DWORD WINAPI Thread1(PVOID param);//线程函数,在该函数中,将启动外挂窗口
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*
lpReserved)
{
 DllHinst = hinst;//载入Dll
 return 1;
}
extern
"C" __declspec(dllexport)bool EnableHook(DWORD dwThreadId)
//
导出函数EnableHook()
{
 if (g_hHook == NULL)
 // 安装新钩子
 {
  g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyBoardHook, DllHinst,
dwThreadId);
  /*记得CreateProcess中的参数吗?我们传进的参数是目标程序的主线程ID,表示我们启动的是线程Hook,而不是全局Hook,这样不会对其他程序产生任何影响*/
 }
 if (g_hHook)
 {
  return true;
 }
 return false;
}
extern "C" __declspec(dllexport)bool DisableHook() //
导出函数DisableHook()
{
 /*卸载Hook,现在暂时先这样了,其实在真实的情况下如果要做的完美的话,需要做许多事情,如果直接关闭客户端的话,这样就足够了,这个函数其实并没有任何的用处,这里仅仅是为了说明外部可以主动控制外挂的启动和关闭而已*/
 if (g_hHook != NULL)
 {
  UnhookWindowsHookEx(g_hHook);
  g_hHook = NULL; // 卸掉新钩子
  return true;
 }
 return false;
}
LRESULT CALLBACK KeyBoardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
 if (nCode >= 0)
 {
  if (wParam == 123)
   //123为F12的键码,可以查看MSDN或者Windows API参考方面的书找到,自己写个小程序测试也可以
  {
   if
(hThread == NULL)
   //这里确保线程启动一次,而不是多次,每一次的启动都回引入一个外挂窗口
   {
    hThread = CreateThread(NULL, 0, Thread1, NULL, NULL, &ThreadID);
    //启动线程,该线程很快执行完毕
   }
  }
 }
 return
(CallNextHookEx(g_hHook, nCode, wParam, lParam));//剩下的让目标程序去处理
}  
  Dll中的函数是外挂程序的核心。在线程启动成功之后,就可以卸载Hook了,这里只是为了简便,所以将Hook仍然保留。
  在CB中编程的时候,最好将程序的文件名保存成你想要的名字,别用默认的名字,默认的名字是Unit+数字组成,而不是类名之类的,这是我不喜欢CB的一个原因,另外一个原因是没有全屏幕专家界面,编写代码的时候其他的太碍事,第三个原因是可以在任意的地方写代码,我的代码又没有具体的风格,经常造成莫名其妙的错误。喜欢他的原因是因为用它开发东西太快了,而且方便,比在VC中默写代码方便多了。
  废话说完,可以添加外挂窗口的视图,在New菜单中选择New Form,如果你想New其他的话我不反对,能不能得到正确的结果我就不知道了。
  网上有不少人问怎么在游戏中弹出外挂窗口,我已经回答的有点不耐烦了。在CB中做是最方便的事情了,但得设置好控件的属性,因为我对使用VC来编写界面不熟悉,更多的时候我都是直接使用API来编写的(我没有学习MFC的打算),所以对于喜欢VC的朋友们只能说抱歉了。
  对于新窗体的属性设置是最重要的,要不然莫名其妙的错误和结果让人变得神经。下面是我对dll中窗体属性的一些总结,如果你有其他不懂的地方,可以给我E-Mail或者MSN或者QQ。
  1、 Visible属性一定要为false,否则窗体没办法移动
  2、 FormStyle属性最好为fsNormal,但一定不要是fsMDIChild或者fsMDIForm,这两个会引起莫名其妙的错误。
  3、
如果刚开始弹不出来的话,将BorderStyle属性改为bsDialog。我在后面的教程中由于要重载TForm的函数,因此这里是不是必须为bsDialog不太清楚了,毕竟是很早以前的代码了。
  其他的注意点好像没有了。下面是CreateThread调用中的Thread1函数实现:
DWORD WINAPI Thread1(PVOID param)
{
 TwgHookForm* wgHookForm;
 wgHookForm = new TwgHookForm(NULL);
 wghandle = wgHookForm->Handle;
 /*暂时将下面的发送消息屏蔽掉,我在窗体创建之后需要窗体做一部分必要动作,所以采用发送消息的机制来了,其实并不是必需这么做的,由许刚开始编写的时候,APIHOOK中的内容都是通过Message的方式来做的,这里为了方便就通过Message来弄了,算是点历史原因吧*/
 SendMessage(wghandle, WM_USER + 2, NULL, NULL);
 wgHookForm->ShowModal();
 delete wgHookForm;
 return 1;
}  
  到现在为止应该可以在游戏中弹出外挂窗口了,我们的第一步也算完成了,外挂程序的平台到现在为止搭建完了,剩下的就是工具的制作和必要代码的编写。在下一节中我准备说一下APIHOOK的方法。
妖城欢迎您!

TOP

协议型网络游戏外挂制作之APIHOOK补遗

上一次我们说了外挂中APIHOOK和基本平台的搭建,但仅仅只用APIHOOK在大多数的时候并不能达到目的,没有哪个公司会将网络打包加密解密解包的程序放在dll中以函数的形式就可以查看。所以这节来讲解为目标程序打内存补丁来修改程序的执行路径,通过这种办法让目标程序成为我们的奴隶。
  前两天由于项目的关系,耽搁了两天教程,对不起大家。今天写教程时才发现,我当时研究程序文件用的是ida
4.17的版本,前段时间刚刚换成了4.7版本。虽然现在的版本比以前更好用,但版本的不兼容却让我头痛,最后没办法又找了个4.17的版本来用。这节中还需要使用的另外一个工具是W32dasm,W32dasm的快捷和为程序打补丁的方便是我最喜欢他的原因,ida
4.7是否支持和W32dasm那样的功能我还不知道,毕竟我刚刚装上,还没怎么用过呢。
  废话就不说了,为程序打内存补丁和做游戏修改器差的不太多,只是游戏修改器修改的是数据,而内存补丁修改的是指令,在做之前,请一定要做好祈祷,虽然我会尽可能的将我所知道的注意点说出来,但仍然不敢保证你是否会出现非法指令、内存越界等等,如果出现蓝屏一定要买彩票阿,反正我是没有遇到过。
  关于如何做内存补丁,在看雪出的那本《加密与解密》上有详细的讨论,这里简单说一下注意点:
  1、 尽可能的调用程序本身的函数,除非没有办法再自己加载函数。
  2、 在调用函数之前,一定要将相关函数的寄存器内容保存,在调用结束后,要将其内容恢复。
  3、 注意堆栈平衡。
  4、
由于我们要给源程序添加代码,因此尽可能的找一块空余的不会被修改内存(内存页4K对齐,这样的内存应该不难找),如果运气差的话,只能自己来申请内存了。
  对于xy2.exe的程序来说,程序里面使用了SendMessage函数,这个函数使用方便,居有很大的广泛性就不用说了。因此我们准备使用该函数来将相关内容发送给外挂程序。不过在做这部分之前,我准备先给程序打补丁来防止目标程序升级来练练手。
  作外挂最主要的工作是对目标程序的分析程度,分析程序主要用的是汇编的知识,有时候我想将汇编说成是调试程序更确切些,因为我大部分时间都是在用汇编调试,只有很少一部分时间来用汇编写程序。
  将xy2.exe的程序用ida反编译,这里用的xy2.exe的版本是9.16之前的版本,从8.12之后到9.16之前xy2.exe的程序都没有做任何改动,因此这段时间的任意一个都是可以的。
  移到地址.text:0042BB80处,这里就不给出代码了,代码太长了,足足有8个页面。这里大概就是WinMain函数,我不知道Ida4.7会将这里作为那个函数名,我用的是4.17,给的函数名是:sub_0_42BB80
proc near。通过研究该段代码,我们可以发现这段代码是函数的主程序,功能有下面的几点:
  1、 检查更新,启动更新程序。
  2、 查看是否播放录像文件,并将程序设置为相应的状态。
  3、 设置程序运行的速度。
  程序整理后的C代码我就不写了,根据ida给出的程序流程图很容易写出来的。
  研究程序可以发现在地址.text:0042BC13处经过判断之后,就会检查大话程序的5个主版本的文件,通过调用下面的函数:
.text:0042BC15 0B8 call sub_0_4480E0
.text:0042BC1A 0B8 call sub_0_4481C0
.text:0042BC1F 0B8 call sub_0_4482A0
.text:0042BC24 0B8 call
sub_0_448380
.text:0042BC29 0B8 call sub_0_448460
.text:0042BC2E 0B8
push ebx
.text:0042BC2F 0BC call sub_0_4488E0
.text:0042BC34 0BC add
esp, 4
  对于之前的这个判断,我想可能是播放录像用的。在接下来的5个call之后,调用sub_0_4488e0来调用升级用的对话框。要修改程序避免升级就简单多了,只用将这个跳转跳过去就行了。这里不用考虑堆栈的平衡,从ida给出的堆栈指针来看,跳转前后的堆栈是一样的。
因此在我们调用CreateProcess之后,就可以通过WriteProcessMemory来进行修改了,将跳转指令的前一个字节改为0xEB,后面的那个字节不用动了,后面的那个字节是用来控制跳转的距离的。这样原来的代码就由:
.text:0042BBE9 0B8 jz short loc_0_42BBF1
  变为
.text:0042BBE9 0B8 jmp short loc_0_42BBF1
  如果你不愿意通过程序来调用,也可以直接修改xy2.exe文件来做到,不过建议将xy2.exe文件备份一个,方便以后升级用。
  对于避免弹出主页,也可以通过相同的办法,但修改的不是一个跳转指令了。这里仅仅给出一点点提示,可以自己去尝试:
  1、 xy2.exe通过调用ShellExecute函数来弹出主页,可以根据引用段来快速找到调用的办法。
  2、 在修改的时候,注意堆栈的平衡点。
  上面的都是对于9.16之前的版本,之前的版本没有给程序加壳,可以这么来做。但9.16之后的程序都是被加了壳的,这样做就不可以了。不过可以先将程序脱壳。大话的注程序使用的是PECompact的壳,脱这个壳有个最快速的办法,这也是我在郁闷了几小时之后才发现的。刚开始的时候我用peid版本太低,竟然不认识这个壳,老实的我只好手动脱壳。脱完之后在看雪的站上转转,看有什么新的点子没有,发现peid的V.92版本,比我的新多了,就下载下来看看,发现是PECompact2.X的壳,更重要的发现是这个版本的peid可以脱比较简单的壳了,就在插件里面躺着,随便一试,发现竟然可以直接脱掉,这更过程不用1分钟。脱壳之后就可以按照之前的办法弄了,不过目标程序改为了脱壳后的文件,最新版本的xy2.exe程序(10.30之前)的免升级跳转点在
.text:0042BA7B jnz short loc_42BAA8
  免弹出主页修改地址在:
.text:00444840 mov eax, [esp+8+var_8]
  今天这节就讲解这些,并没有用到w32dasm,下一节主要讲解用于拦截数据的内存补丁,将会频繁使用w32dasm。
  非常感谢热心网友alan将之前的教程整理成为VC的版本,如果对此版本有什么疑问的话,可以给他发E-mail:tyr_alan@hotmal.com。alan整理出来的源文件我会随着教程和我拿到的版本粘贴在www.gameres.com上,源文件的版本可能和教程的内容不符合。
  附:
  修改地址在:
.text:00444840 mov eax, [esp+8+var_8]
  今天这节就讲解这些,并没有用到w32dasm,下一节主要讲解用于拦截数据的内存补丁,将会频繁使用w32dasm。
  非常感谢热心网友alan将之前的教程整理成为VC的版本,如果对此版本有什么疑问的话,可以给他发E-mail:tyr_alan@hotmal.com。alan整理出来的源文件我会随着教程和我拿到的版本粘贴在www.gameres.com上,源文件的版本可能和教程的内容不符合。
妖城欢迎您!

TOP

发新话题