如何面对高水平的破解组织 谈暴力破解应对

2005-05-09, 11:18 sanit

  共享软件是目前世界上软件业比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动获得丰厚的回报;但,实际并非如此,绝大多数的人都铩羽而归。值得注意的是:除了软件设计和技术上的原因外,最大的原因就是共享软件被破解(Crack)了……
  
  面对破解
  
  一个做共享软件的作者,面对的是已经形成团伙的众多破解高手,国内的什么CCG、BCG,国外的eGis、King、Core、TNT、DAMN和TMG,皆为水平一流的破解组织。全球盗版软件不少于80%都是由他们的破解的,技术实力连大软件公司都不可小视。
  
  看到这里,你是否已经灰心了?别怕,虽然目前我们理论上无法完全避免被破解,但如果能够有效地拖延被破解的时间,并充分打击破解者的自信心,是可以让破解者无法忍受这种折磨从而最终放弃的。
  
  破解,通常的做法有两种——暴力破解(爆破)和写注册机。笔者就自己积累的经验来依次讲解每种破解方法的原理和应对方法,某些关键的例程讲解(Delphi代码),使用C++和VB的朋友可以自己稍微修改一下。希望这能对一些新手有些帮助,能够更有效地保护自己的劳动成果。
  
  认识暴力破解
  
  暴力破解简称“爆破”,这是破解手段中最常见的,也是最简单的破解方法。该法最适合于对付没有CRC校验的软件,破解新手乐于采用。
  
  大凡共享软件,验证是否注册大多采用if条件语句来进行判断,即使你采用了什么RSA或ECC等强力加密算法,也免不了使用if条件语句。这里就是共享软件最为危险的地方,也是爆破手孜孜不倦所寻求的目标。
  
  例如,你的注册函数类似如下:
  
  {利用RSA进行注册码的数字签名验证}
  if RSAVerify(MD5(Key),MD5(Code),e,n)then
  ShowMessage(’注册成功!’)
   else
  ShowMessage(’注册失败!’);
  
  {这里Key是用户输入的注册码,是由你发送给注册用户的,Code是根据用户输入的用户名自动计算出来的注册码,e是RSA算法的公匙,而n是RSA算法的模数。}

  第一次过招
  
  上例注册函数即使使用了强劲的RSA算法进行注册码验证,但依然很容易被破解,我们只要把这里修改为:
  
  将逻辑判断改为否 
  if not RSAVerify(MD5)Key),MD5(Code),e,n)then
  ShowMessage(’注册成功!’)
  else
  ShowMessage(’注册失败!’);
 
   这时戏剧性的结果会产生:随便输入任何注册码都可以注册通过,相反输入正确的注册码却无法通过注册。
  
  要破解这样的软件就必须先反汇编或者跟踪你的程序,找到判断注册码的cmp、test等汇编指令后的关键跳转指令处,通常是je、jz之类的汇编指令,把它们修改为jne或jnz即可,这样常常只需要修改一个字节就可以完美破解了。
  
  目前大部分共享软件都是用以上方法进行判断,这也是为什么网上被破解的软件铺天盖地的主要原因。因为这样破解实在是太简单了……
  
  第二次过招
  
  其实只要把软件的关键代码嵌入到注册码或者注册文件中就可以充分防止破解。
  
  最简单的方法就是把关键代码(你的软件功能限制部分最关键而且最简单的一个函数)做成一个小DLL(动态链接库),用强力对称算法加密(密匙可以是主程序某一固定不变的部分或壳的特征Hash值)后生成一个注册文件(License文件,这格式只有你知道),或者Base64编码后生成一个注册表文件,用户可以双击导入注册表内。
  
  校验流程如下:已注册用户验证注册码时,先验证有没有文件,没有文件则自然受限制的功能无法使用。如果有注册文件,解密后即生成一个小临时文件。如果主程序被脱壳或者被修改(爆破),自然Hash值密码不符,解密出来的肯定都是垃圾码,没有一点用处。只有没有被修改的主程序才能正确地解码,而且当然只有解密正确的文件才是一个真正的DLL文件,才能被GetProcAddress函数找到欲调用的关键函数地址。这样只有已注册用户才可以享受到你的软件的全部功能了。如此一来,Cracker破解你的软件就变得很困难了。
  
  首先,他如果没有注册文件,即使他把主程序脱壳了,由于受限制的部分和注册文件是关联的,他也根本无法修补完整。
  
  第二,即使他得到了你的注册文件,由于是加密文件,他也无法直接利用,这样就逼迫他去拆解你的算法,这可是他们最不愿意碰到的事情啊!如果到了这一步,只有真正对加密算法有研究的Cracker高手才会继续破解下去。
  
  第三,你是可以用些小技巧来使他的破解工作更加繁锁。这里我推荐大家使用DSA公开密匙加密算法,它和RSA一样,可以进行数字签名(RSA还可以加密,DSA则只能进行数字签名)。笔者这里选用它的原因就是它有一项非常实用的特性:随机数填充机制。即DSA每次签名都要使用一个随机数K,正因为有这个K的存在,即使是相同的用户名和机器识别码,由DSA加密过的每份注册文件都不会相同。这对Cracker拆解你的注册文件来说是一个极大的障碍。

  第四,即使他得到了解密后的DLL文件,他也需要大幅度地修改主程序或者把你的DLL部分的关键代码拆出来添到主可执行文件中。这就看他对PE文件格式理解得如何了。即使这样,如果你的程序中有大量的Hash校验和死机代码,你就耐心等着我们可爱的Cracker同志吐血吧……:)
  
  最后还要记住:用完这个DLL临时文件后立即从内存中卸载此DLL并删掉,而且注意在解密之前探测一下,系统中有没有FileMon这个威胁极大的探测器:
  
  {探测FileMon}
  function DetectFileMon:Boolean;
  begin
  if CreateFile(PChar(’//./FILEVXD’),
  GENERIC_READ or GENERIC_WRITE,
  FILE_SHARE_READ or FILE_SHARE_WRITE
  nil,
  OPEN_EXISTING,
  FILE_ATTRIBUTE_NORMAL,
  0  INVALID_HANDLE_VALUE then
  Result:= True //如果有,就Down机!
  else
  Result:= False;
  end;
  
  当然,你可以保护得更好一些:可以不采用临时DLL,而把解密后的关键代码用WriteProcessMemory这个API函数写入到主可执行文件自己进程被提交(Committed)的内存页面的指定位置去。这样由于磁盘上没有解密后的临时文件,破解更加困难。事实上,目前世界上最强劲的专业保护软件Amadillo就是用的这种方法。而且这种方法可以充分防止被调试器Dump。但实现起来比较困难,尤其是在WinNT 5以后的操作系统中。
  
  由于这种方法将注册文件和受限制代码惟一关联,爆破手拿到你的软件也只有干瞪眼。建议大家都给共享软件加上功能限制,这样比时间和次数限制更加安全。

归类于: 未分类 — nirvana @ 2:51 am 评论(0)
十二月 26, 2006
关于内存地址的个人理解和操作原理

最近看见太多人询问内存地址的作用和使用方法,我来做个大概的解释吧
     内存地址,其实电脑的每个操作都会在内存中先存储,然后经CPU处理后返回内存最终显现出来,而内存里有个内存地址,是为了区分各种不同数据的,而每个地址则相对应一个数据。
    网络游戏中,数据也会先存放到内存中,然后进行处理,包括坐标、血量、MANA等,其实所有信息都是在内存中一一存放并等待处理。处理完毕后结果将会返回原来的地址中(当然也有返回到其他的)。
    通过以上的解释,大家可以明白一个道理,其实任何数据都是会下载到本地电脑上然后经过处理后再返回的(至于返回到网络和返回到本地就看数据的重要性了)包括网页的浏览等,任何在你电脑上可以看见的东西必定先存放到本地内存中或硬盘中等待处理后才会显现出来的。

内存和外挂:
    说到这里当然要给大家举个例子了,就拿网络游戏来说吧。以前的网络游戏很多数据都是在本地电脑上进行处理和比对的,例如曾经的精灵,它的游戏币是在本地电脑上进行处理的,处理后的结果再返回服务器,这样就导致了当时的刷钱外挂,因为大家可以修改本地电脑上的内存地址中的数值,然后再返回给服务器。从以上可以知道单机游戏锁血锁蓝是多么简单的事了吧。因为所有的数据操作都是在本地运行计算的。
    当然,还有的外挂是通过改变封包数据来达到效果的。例如魔兽世界加速外挂,比如我现在人物的坐标是0  0然后按照正常速度走的话下一秒坐标是1  1。因为魔兽世界是在本地对人物坐标进行处理,如果只是单一的改变本地游戏坐标的位置,或许你在自己电脑上会看见自己一下跑到 3  3的位置了,但实际游戏服务器上的人物还在1  1这个位置,那就毫无意义。我们可以截取发送坐标的封包,修改我们下一秒跨度的数值,向服务器发送我下一秒会在3  3的位置,这时服务器处理后你的人物就在3  3的位置了。当然,整个过程只是改变封包内的数据值,并没有向服务器多发送封包,所以有的人说是加速外挂给游戏带来了压力的说法是错误的。当然,运营商也可以通过检查你的日常数据来判断你是否使用外挂,所以是没有查不出的外挂,只有不愿意查的外挂。
    而现在的网络游戏血、经验、等级、金钱等重要数据都是由服务器计算然后返回给客户端(就是我们),而且每次存放血、蓝等信息的内存地址在每次游戏的启动都会变化,在现在来说,修改其数值已经没有任何意义了,因为那只能让你在本地读取的数值不同而已,而真正的数据被存储在了服务器上,你无法修改。但是我们可以通过读取内存地址中的数值来精确我们脚本的计算。

脚本与读取内存地址:
    例如我要做一个脚本,需要在血或蓝少于一个具体的值的时候加血加蓝,这时候我们就可以采用内存地址的读取,因为这种做法可以让脚本以最精确的方式去加血加蓝,而不是去看血条蓝条(其实血条蓝条这时候已经根本不重要)。在按键精灵里
有这么一个脚本。
VBSCall ReadMemory(内存地址:整数(16进制),类型:0-字节 1-整数 2-长整数,输出值-所读取的内容:长整型变量)

实际应用(中间的内存地址为假设):
VBSCall ReadMemory( &H400000,2,xue)
这句脚本的意思为,&H400000为读取内存地址400000中数值;2为读取类型,为长整数;xue则把读取出来的数值保存到xue这个变量中去。
如果这个400000的地址是用来储存血量的。那我们就可以对xue这个变量来进行判断计算。实际应用如下

If xuea and y>b
   moveto 300  500
  //以上坐标为虚拟,根据游戏不同自己更改,以下放入这个游戏里移动的移动方式的脚本
endif
//如果两个坐标都大于我们希望保存的地方,我们就开始向另一个方向移动

由此我们可以生成
if xa and yb
if x=a and y=b
一共5种不同的处理方法,这样让人物回到原来的点继续开始打怪就可以轻松制作原点挂机的脚本
 Windows, 未分类 — nirvana @ 11:27 pm 评论(0)

说了那么多,一定会产生一个很大的问题,如何查找游戏内的内存地址。
内存地址的查找
     我一般用金山游侠 例如我要找红,先在自己满血的时候输入上限,然后搜索,这时候会搜索出很多地址,一些是代表你当前的红,和你的上限,还有一些正好是搜索时碰巧一样的。
     OK,在结果里再搜索一次,会去掉一些,这样搜索3次基本上结果已经很少了,但还不准确。
     现在,你要做的是就是让自己死了,不同游戏可能不同,死的时候血是0,那就去自杀吧(如果掉经验用小号)。
     在结果里用0搜索一次。
     别以为这样就结束了,这次要让自己不死,那就让自己少点血,我一般采用脱掉加血上限的装备,让自己的血减少。
     依照以上方法反复尝试,最后确定一个地址。
     地址出现以后别高兴太早,现在很多游戏内存地址是变动的(至少每次开游戏时就变动),所以可能这次管用,到下次还要重新搜索。

归类于:
编写游戏外挂原理

今天石器上不去,没法调程序,写篇文章给想学写外挂的朋友参考

一、先说一下写一个外挂需要什么条件

1、熟练的C语言知识

目前的外挂大部分都是用BC或者是vc写的,拥有熟练的C语言知识是写外挂的基本条件

2、具有很强的汇编基础

一般游戏都不可能有原代码的,必须靠反汇编或者跟踪的办法来探索其中的机理

,所以有强的汇编基础也是必不可少的条件

3、熟练掌握跟踪和调试的工具

有了上面2个条件后,掌握一些工具也是很有必要的

跟踪的工具,softice当然是不二之选,至于反汇编的工具,我推荐用IDA PRO

这个工具反汇编出来的代码结构清晰,非常好读

如果你不具有上面的条件,还是先把基础打好,再来写外挂吧,一分耕耘,一分收获,天下没有白掉的馅饼的

二、写外挂面临的基本技术问题

1、修改进程的执行代码

要修改进程的执行代码,要先取得进程的ID,如果是由外挂程序启动,返回值里就有进程ID,如果不是的话,

需要用findwindow找到窗口句柄,再用GetWindowProcessID取得进程ID,取得进程ID以后,就可以用

writeprocessmemory来修改进程的执行代码了,使程序按照我们的意愿来执行,石器外挂里的不遇敌、寸步遇敌

就是用这样的方法来实现的

2、截获外挂发送和接收的封包

除了通过修改代码来实现的功能以外,很多的功能都是通过修改封包来实现的,要修改封包,首先要能截获它。

第一步是要跟踪出发和收的位置,至于怎么跟踪,我以后会提到,找到位置以后,有2个办法,一是在那个位置加一

个jmp语句,跳到你的处理函数位置,处理完后,再跳回来,这种方法要求比较高,需要处理好很多事情,另一种办法

是往那个位置写条能造成例外的指令,比如int 3,然后用DebugActiveProcess调试游戏进程,这样每当游戏执行到那个

位置的时候,就会停下来,到外挂程序里面去,等外挂程序处理完以后,用ContinueDebugEvent 继续运行程序。

今天先写这么多,下回将讨论外挂的具体功能该怎么实现

今天来谈谈地址的调查问题,地址调查是写外挂中最艰辛,最富有挑战性的事情,很多朋友问我要外挂的原程序,其实有了外挂原程序,如果你不会调查地址,还是没用的,

原程序和地址的关系就象武学中招式与内功的关系,没有内功的招式,只是一个花架子。而内功精深以后,任何普通的招式,都有可能化腐朽为神奇,外挂中的地址分为两类,一类是程序地址,一类是数据地址。象石器中的双石器,真彩,不遇敌,寸步遇敌,发送接收封包等,都属于第一类,而人物坐标,状态等,都属于第二类。对于第一类地址,主要依靠softice来调查地址,对第二类地址,可以用一些游戏工具,比如fpe,game expert,game master等来调查,我一直用game expert,因为我找不到2000下能用的fpe,

各位以前用fpe改游戏的时候,没想过他也能用来干这个吧

对于第二类数据的调查方法,大部分人都很熟习了,我就不多说了,现在主要来谈谈第一类数据的详细调查过程,比如我们要调查发送封包的位置,如何着手呢,客户端往服务器要发很多封包,但最简单的办法莫过从说话的封包入手,先说一句很长的话,最好是英文,查起来方便,说完以后,用任意一种办法进入游戏程序的进程空间(比如先用spy查出游戏程序的窗口句柄,再切换到softice打入bmsg 窗口句柄 wm_lbuttondown,这样在游戏程序中一点鼠标就进入了他的进程空间)然后用s命令查出这句话所放的内存地址,记下这个地址,在softice中打入bpm 刚才调查到的地址,这个指令的意思是只要有访问这个内存的动作,立刻中断,然后再切换到游戏,说一句话,你会发现softice自动中断到某一个位置了,从这个位置跟踪下去,发送封包的位置也就不远了。

上面所说的都是针对一个全新的游戏程序而言,如果是一个老的程序,有前辈做了大量的工作,还可以用些别的办法,如反汇编等,来调查。以后游戏版本的更新也是如此,只要把老版本的地址位置附近的代码记下来,去新版本的代码里面search一下,就ok了。

恩,休息一会儿,休息一会儿

我主要对外挂的技术进行分析,至于游戏里面的内部结构每个都不一样,这里就不做讲解了,我也没有那么厉害,所有的都知道,呵呵!
1 首先游戏外挂的原理
外挂现在分为好多种,比如模拟键盘的,鼠标的,修改数据包的,还有修改本地内存的,但好像没有修改服务器内存的哦,呵呵!其实修改服务器也是有办法的,只是技术太高一般人没有办法入手而已!(比如请GM去夜总会,送礼,收黑钱等等办法都可以修改服务器数据,哈哈)
修改游戏无非是修改一下本地内存的数据,或者截获api函数等等,这里我把所能想到的方法都作一个介绍,希望大家能做出很好的外挂来使游戏厂商更好的完善自己的技术.
我见到一片文章是讲魔力宝贝的理论分析,写的不错,大概是那个样子.
下来我就讲解一下技术方面的东西,以作引玉之用
2 技术分析部分
1 模拟键盘或鼠标的响应
我们一般使用UINT SendInput(
UINT nInputs, // count of input events
LPINPUT pInputs, // array of input events
int cbSize // size of structure
);api函数
第一个参数是说明第二个参数的矩阵的维数的,第二个参数包含了响应事件,这个自己填充就可以,最后是这个结构的大小,非常简单,这是最简单的方法模拟键盘鼠标了,呵呵
注意:这个函数还有个替代函数:
VOID keybd_event(
BYTE bVk, // 虚拟键码
BYTE bScan, // 扫描码
DWORD dwFlags,
ULONG_PTR dwExtraInfo // 附加键状态
);和
VOID mouse_event(
DWORD dwFlags, // motion and click options
DWORD dx, // horizontal position or change
DWORD dy, // vertical position or change
DWORD dwData, // wheel movement
ULONG_PTR dwExtraInfo // application-defined information
);
这两个函数非常简单了,我想那些按键精灵就是用的这个吧,呵呵,上面的是模拟键盘,下面的是模拟鼠标的.
这个仅仅是模拟部分,要和游戏联系起来我们还需要找到游戏的窗口才行,或者包含快捷键,就象按键精灵的那个激活键一样,我们可以用GetWindow函数来枚举窗口,也可以用Findwindow函数来查找制定的窗口(注意还有一个FindWindowEx),FindwindowEx可以找到窗口的子窗口,比如按钮,等什么东西.当游戏切换场景的时候我们可以用FindWindowEx来确定一些当前窗口的特征,从而判断是否还在这个场景,方法很多了,比如可以GetWindowInfo来确定一些东西,比如当查找不到某个按钮的时候就说明游戏场景已经切换了,等等办法.有的游戏没有控件在里面,这是对图像做坐标变换的话,这种方法就要受到限制了.这就需要我们用别的办法来辅助分析了.
至于快捷键我们要用动态连接库实现了,里面要用到hook技术了,这个也非常简单,大家可能都会了,其实就是一个全局的hook对象然后SetWindowHook就可以了,回调函数都是现成的,而且现在网上的例子多如牛毛,这个实现在外挂中已经很普遍了.如果还有谁不明白,那就去看看msdn查找SetWindowHook就可以了.

这个动态连接库的作用很大,不要低估了哦,它可以切入所有的进程空间,也就是可以加载到所有的游戏里面哦,只要用对,你会发现很有用途的!
这个需要你复习一下win32编程的基础知识了,呵呵,赶快去看书吧!

2截获消息
有些游戏的响应机制比较简单,是基于消息的,或者用什么定时器的东西,这个时候你就可以用拦截消息来实现一些有趣的功能了.
我们拦截消息使用的也是hook技术,里面包括了键盘消息,鼠标消息,系统消息,日志等,别的对我们没有什么大的用处,我们只用拦截消息的回调函数就可以了,这个不会让我写例子吧,其实这个和上面的一样,都是用SetWindowHook来写的,看看就明白了很简单的.
至于拦截了以后做什么就是你的事情了,比如在每个定时器消息里面处理一些我们的数据判断,或者在定时器里面在模拟一次定时器,那么有些数据就会处理两次,呵呵,后果嘛,不一定是好事情哦,呵呵,不过如果数据计算放在客户端的游戏就可以真的改变数据了,呵呵,试试看吧!用途还有很多,自己想也可以想出来的,呵呵!

3拦截socket包
这个技术难度要比原来的高很多哦,要有思想准备.
首先我们要替换winSock.dll或者winsock32.dll,我们写的替换函数要和原来的函数一致才行,就是说它的函数输出什么样的,我们也要输出什么样子的函数,而且参数,参数顺序都要一样才行,然后在我们的函数里面调用真正的winSock32.dll里面的函数就可以了
首先:我们可以替换动态库到系统路径
其次:我们应用程序启动的时候可以加载原有的动态库,用这个函数LoadLibary
然后定位函数入口用GetProcAddress函数获得每个真正socket函数的入口地址
当游戏进行的时候它会调用我们的动态库,然后从我们的动态库中处理完毕后才跳转到真正动态库的函数地址,这样我们就可以在里面处理自己的数据了,应该是一切数据.呵呵!
兴奋吧,拦截了数据包我们还要分析之后才能进行正确的应答,不要以为这样工作就完成了,呵呵!还早呢,等分析完毕以后我们还要仿真应答机制来和服务器通信,一个不小心就会被封号,呵呵,呜~~~~~~~~我就被封了好多啊!
分析数据才是工作量的来源呢,游戏每次升级有可能加密方式会有所改变,因此我们写外挂的人都是亡命之徒啊,被人娱乐了还不知道,呵呵!(声明我可没有赚钱,我是免费的)
好了,给大家一个不错的起点,这里有完整的替换socket源代码,呵呵!
http://www.vchelp.net/vchelp/zsrc/wsock32_sub.zip

4截获api
上面的技术如果可以灵活运用的话我们就不用截获api函数了,其实这种技术是一种补充技术.比如我们需要截获socket以外的函数作为我们的用途,我们就要用这个技术了,其实我们也可以用它直接拦截在socket中的函数,这样更直接.
现在拦截api的教程到处都是,我就不列举了,我用的比较习惯的方法是根据输入节进行拦截的,这个方法可以用到任何一种操作系统上,比如98/2000等,有些方法不是跨平台的,我不建议使用.这个技术大家可以参考windows核心编程里面的545页开始的内容来学习,如果是98系统可以用window系统奥秘那个最后一章来学习.
好了方法就是这么多了,看大家怎么运用了,其它的一些针对性的技巧这里我就不说了,要不然会有人杀了我的,呵呵!

记住每个游戏的修改方法都不一样,如果某个游戏数据处理全部在服务器端,那么你还是别写外挂了,呵呵,最多写个自动走路的外挂,哈哈!
数据分析的时候大家一定要注意,不要轻易尝试和服务器的连接,因为那有很危险,切忌!等你掌握了大量的数据分析结果以后,比较有把握了在试试,看看你的运气好不好,很有可能会成功的哦,呵呵!
其实像网金也疯狂的那种模拟客户端的程序也是不错的,很适合office的人用,就看大家产品定位了.
好了不说了,大家努力吧!切忌不要被游戏厂商招安哦,那样有损我们的形象,我们是为了让游戏做的更好而开发的,也不愿意打乱游戏的平衡,哎,好像现在不是这样了!不说了随其自然吧!

归类于: Windows, 未分类 — nirvana @ 11:26 pm 评论(1)
外挂编写原理

一、 前言

  所谓游戏外挂,其实是一种游戏外辅程序,它可以协助玩家自动产生游戏动作、修
改游戏网络数据包以及修改游

戏内存数据等,以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然,现
在对游戏外挂程序的“合法”

身份众说纷纭,在这里我不想对此发表任何个人意见,让时间去说明一切吧。

  不管游戏外挂程序是不是“合法”身份,但是它却是具有一定的技术含量的,在这
些小小程序中使用了许多高端

技术,如拦截Sock技术、拦截API技术、模拟键盘与鼠标技术、直接修改程序内存技术
等等。本文将对常见的游戏外挂

中使用的技术进行全面剖析。

  二、认识外挂

  游戏外挂的历史可以追溯到单机版游戏时代,只不过当时它使用了另一个更通俗易
懂的名字??游戏修改器。它可

以在游戏中追踪锁定游戏主人公的各项能力数值。这样玩家在游戏中可以达到主角不掉
血、不耗费魔法、不消耗金钱

等目的。这样降低了游戏的难度,使得玩家更容易通关。

  随着网络游戏的时代的来临,游戏外挂在原有的功能之上进行了新的发展,它变得
更加多种多样,功能更加强大

,*作更加简单,以至有些游戏的外挂已经成为一个体系,比如《石器时代》,外挂品
种达到了几十种,自动战斗、

自动行走、自动练级、自动补血、加速、不遇敌、原地遇敌、快速增加经验值、按键精
灵……几乎无所不包。

  游戏外挂的设计主要是针对于某个游戏开发的,我们可以根据它针对的游戏的类型
可大致可将外挂分为两种大类

  一类是将游戏中大量繁琐和无聊的攻击动作使用外挂自动完成,以帮助玩家轻松搞
定攻击对象并可以快速的增加

玩家的经验值。比如在《龙族》中有一种工作的设定,玩家的工作等级越高,就可以驾
驭越好的装备。但是增加工作

等级却不是一件有趣的事情,毋宁说是重复枯燥的机械劳动。如果你想做法师用的杖,
首先需要做基本工作–?砍树。

砍树的方法很简单,在一棵大树前不停的点鼠标就可以了,每10000的经验升一级。这
就意味着玩家要在大树前不停的

点击鼠标,这种无聊的事情通过”按键精灵”就可以解决。外挂的”按键精灵”功能可以让
玩家摆脱无趣的点击鼠标的工

作。

  另一类是由外挂程序产生欺骗性的网络游戏封包,并将这些封包发送到网络游戏服
务器,利用这些虚假信息欺骗

服务器进行游戏数值的修改,达到修改角色能力数值的目的。这类外挂程序针对性很
强,一般在设计时都是针对某个

游戏某个版本来做的,因为每个网络游戏服务器与客户端交流的数据包各不相同,外挂
程序必须要对欺骗的网络游戏

服务器的数据包进行分析,才能产生服务器识别的数据包。这类外挂程序也是当前最流
利的一类游戏外挂程序。

  另外,现在很多外挂程序功能强大,不仅实现了自动动作代理和封包功能,而且还
提供了对网络游戏的客户端程

序的数据进行修改,以达到欺骗网络游戏服务器的目的。我相信,随着网络游戏商家的
反外挂技术的进展,游戏外挂

将会产生更多更优秀的技术,让我们期待着看场技术大战吧……

  三、外挂技术综述

  可以将开发游戏外挂程序的过程大体上划分为两个部分:

  前期部分工作是对外挂的主体游戏进行分析,不同类型的外挂分析主体游戏的内容
也不相同。如外挂为上述谈到

的外挂类型中的第一类时,其分析过程常是针对游戏的场景中的攻击对象的位置和分布
情况进行分析,以实现外挂自

动进行攻击以及位置移动。如外挂为外挂类型中的第二类时,其分析过程常是针对游戏
服务器与客户端之间通讯包数

据的结构、内容以及加密算法的分析。因网络游戏公司一般都不会公布其游戏产品的通
讯包数据的结构、内容和加密

算法的信息,所以对于开发第二类外挂成功的关键在于是否能正确分析游戏包数据的结
构、内容以及加密算法,虽然

可以使用一些工具辅助分析,但是这还是一种坚苦而复杂的工作。

  后期部分工作主要是根据前期对游戏的分析结果,使用大量的程序开发技术编写外
挂程序以实现对游戏的控制或

修改。如外挂程序为第一类外挂时,通常会使用到鼠标模拟技术来实现游戏角色的自动
位置移动,使用键盘模拟技术

来实现游戏角色的自动攻击。如外挂程序为第二类外挂时,通常会使用到挡截Sock和挡
截API函数技术,以挡截游戏服

务器传来的网络数据包并将数据包修改后封包后传给游戏服务器。另外,还有许多外挂
使用对游戏客户端程序内存数

据修改技术以及游戏加速技术。

  本文主要是针对开发游戏外挂程序后期使用的程序开发技术进行探讨,重点介绍的
如下几种在游戏外挂中常使用

的程序开发技术:

  ● 动作模拟技术:主要包括键盘模拟技术和鼠标模拟技术。

  ● 封包技术:主要包括挡截Sock技术和挡截API技术。

四、动作模拟技术

  我们在前面介绍过,几乎所有的游戏都有大量繁琐和无聊的攻击动作以增加玩家的
功力,还有那些数不完的迷宫

,这些好像已经成为了角色游戏的代名词。现在,外挂可以帮助玩家从这些繁琐而无聊
的工作中摆脱出来,专注于游

戏情节的进展。外挂程序为了实现自动角色位置移动和自动攻击等功能,需要使用到键
盘模拟技术和鼠标模拟技术。

下面我们将重点介绍这些技术并编写一个简单的实例帮助读者理解动作模拟技术的实现
过程。

  1. 鼠标模拟技术
  
  几乎所有的游戏中都使用了鼠标来改变角色的位置和方向,玩家仅用一个小小的鼠
标,就可以使角色畅游天下。

那么,我们如何实现在没有玩家的参与下角色也可以自动行走呢。其实实现这个并不
难,仅仅几个Windows API函数就

可以搞定,让我们先来认识认识这些API函数。

  (1) 模拟鼠标动作API函数mouse_event,它可以实现模拟鼠标按下和放开等动作。

    VOID mouse_event(
      DWORD dwFlags, // 鼠标动作标识。
      DWORD dx, // 鼠标水平方向位置。
      DWORD dy, // 鼠标垂直方向位置。
      DWORD dwData, // 鼠标轮子转动的数量。
      DWORD dwExtraInfo // 一个关联鼠标动作辅加信息。
    );

  其中,dwFlags表示了各种各样的鼠标动作和点击活动,它的常用取值如下:

   MOUSEEVENTF_MOVE 表示模拟鼠标移动事件。

   MOUSEEVENTF_LEFTDOWN 表示模拟按下鼠标左键。

   MOUSEEVENTF_LEFTUP 表示模拟放开鼠标左键。

   MOUSEEVENTF_RIGHTDOWN 表示模拟按下鼠标右键。

   MOUSEEVENTF_RIGHTUP 表示模拟放开鼠标右键。

   MOUSEEVENTF_MIDDLEDOWN 表示模拟按下鼠标中键。

   MOUSEEVENTF_MIDDLEUP 表示模拟放开鼠标中键。

  (2)、设置和获取当前鼠标位置的API函数。获取当前鼠标位置使用GetCursorPos()
函数,设置当前鼠标位置使用

SetCursorPos()函数。

    BOOL GetCursorPos(
     LPPOINT lpPoint // 返回鼠标的当前位置。
    );
    BOOL SetCursorPos(
    int X, // 鼠标的水平方向位置。
      int Y //鼠标的垂直方向位置。
    );

  通常游戏角色的行走都是通过鼠标移动至目的地,然后按一下鼠标的按钮就搞定
了。下面我们使用上面介绍的API

函数来模拟角色行走过程。

   CPoint oldPoint,newPoint;
   GetCursorPos(&oldPoint); //保存当前鼠标位置。
   newPoint.x = oldPoint.x+40;
   newPoint.y = oldPoint.y+10;
   SetCursorPos(newPoint.x,newPoint.y); //设置目的地位置。
   mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//模拟按下鼠标右键。
   mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//模拟放开鼠标右键。

  2. 键盘模拟技术

  在很多游戏中,不仅提供了鼠标的*作,而且还提供了键盘的*作,在对攻击对象
进行攻击时还可以使用快捷键

。为了使这些攻击过程能够自动进行,外挂程序需要使用键盘模拟技术。像鼠标模拟技
术一样,Windows API也提供了

一系列API函数来完成对键盘动作的模拟。

  模拟键盘动作API函数keydb_event,它可以模拟对键盘上的某个或某些键进行按下
或放开的动作。

   VOID keybd_event(
     BYTE bVk, // 虚拟键值。
     BYTE bScan, // 硬件扫描码。
     DWORD dwFlags, // 动作标识。
     DWORD dwExtraInfo // 与键盘动作关联的辅加信息。
   );

  其中,bVk表示虚拟键值,其实它是一个BYTE类型值的宏,其取值范围为1-254。有
关虚拟键值表请在MSDN上使用

关键字“Virtual-Key Codes”查找相关资料。bScan表示当键盘上某键被按下和放开
时,键盘系统硬件产生的扫描码

,我们可以MapVirtualKey()函数在虚拟键值与扫描码之间进行转换。dwFlags表示各种
各样的键盘动作,它有两种取

值:KEYEVENTF_EXTENDEDKEY和KEYEVENTF_KEYUP。

  下面我们使用一段代码实现在游戏中按下Shift+R快捷键对攻击对象进行攻击。

   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),0,0); //按下CTRL
键。
   keybd_event(0×52,MapVirtualKey(0×52,0),0,0);//键下R键。
   keybd_event(0×52,MapVirtualKey(0×52,0), KEYEVENTF_KEYUP,0);//放开R键。
   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),
   KEYEVENTF_KEYUP,0);//放开CTRL键。

  3. 激活外挂

  上面介绍的鼠标和键盘模拟技术实现了对游戏角色的动作部分的模拟,但要想外挂
能工作于游戏之上,还需要将

其与游戏的场景窗口联系起来或者使用一个激活键,就象按键精灵的那个激活键一样。
我们可以用GetWindow函数来枚

举窗口,也可以用Findwindow函数来查找特定的窗口。另外还有一个FindWindowEx函数
可以找到窗口的子窗口,当游

戏切换场景的时候我们可以用FindWindowEx来确定一些当前窗口的特征,从而判断是否
还在这个场景,方法很多了,

比如可以GetWindowInfo来确定一些东西,比如当查找不到某个按钮的时候就说明游戏
场景已经切换了等等办法。当使

用激活键进行关联,需要使用Hook技术开发一个全局键盘钩子,在这里就不具体介绍全
局钩子的开发过程了,在后面

的实例中我们将会使用到全局钩子,到时将学习到全局钩子的相关知识。

4. 实例实现

  通过上面的学习,我们已经基本具备了编写动作式游戏外挂的能力了。下面我们将
创建一个画笔程序外挂,它实

现自动移动画笔字光标的位置并写下一个红色的“R”字。以这个实例为基础,加入相
应的游戏动作规则,就可以实现

一个完整的游戏外挂。这里作者不想使用某个游戏作为例子来开发外挂(因没有游戏商
家的授权啊!),如读者感兴

趣的话可以找一个游戏试试,最好仅做测试技术用。

  首先,我们需要编写一个全局钩子,使用它来激活外挂,激活键为F10。创建全局
钩子步骤如下:

  (1).选择MFC AppWizard(DLL)创建项目ActiveKey,并选择MFC Extension DLL
(共享MFC拷贝)类型。

  (2).插入新文件ActiveKey.h,在其中输入如下代码:

   #ifndef _KEYDLL_H
   #define _KEYDLL_H

   class AFX_EXT_CLASS CKeyHook:public CObject
   {
    public:
 CKeyHook();
 ~CKeyHook();
 HHOOK Start(); //安装钩子
 BOOL Stop(); //卸载钩子
   };
   #endif

  (3).在ActiveKey.cpp文件中加入声明"#include ActiveKey.h"。

  (4).在ActiveKey.cpp文件中加入共享数据段,代码如下:

   //Shared data section
   #pragma data_seg(”sharedata”)
   HHOOK glhHook=NULL; //钩子句柄。
   HINSTANCE glhInstance=NULL; //DLL实例句柄。
   #pragma data_seg()

  (5).在ActiveKey.def文件中设置共享数据段属性,代码如下:

   SETCTIONS
   shareddata READ WRITE SHARED

  (6).在ActiveKey.cpp文件中加入CkeyHook类的实现代码和钩子函数代码:

   //键盘钩子处理函数。
   extern “C” LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM
lParam)
   {
   if( nCode >= 0 )
   {
   if( wParam == 0X79 )//当按下F10键时,激活外挂。
 {
  //外挂实现代码。
CPoint newPoint,oldPoint;
   GetCursorPos(&oldPoint);
   newPoint.x = oldPoint.x+40;
   newPoint.y = oldPoint.y+10;
   SetCursorPos(newPoint.x,newPoint.y);
   mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//模拟按下鼠标左键。
  mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//模拟放开鼠标左键。
  keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),0,0); //按下SHIFT键。
  keybd_event(0×52,MapVirtualKey(0×52,0),0,0);//按下R键。
  keybd_event(0×52,MapVirtualKey(0×52,0),KEYEVENTF_KEYUP,0);//放开R键。
  keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),KEYEVENTF_KEYUP,0);//放开
SHIFT键。
      SetCursorPos(oldPoint.x,oldPoint.y);
 }
   }
   return CallNextHookEx(glhHook,nCode,wParam,lParam);
   }

   CKeyHook::CKeyHook(){}
   CKeyHook::~CKeyHook()
   { 
   if( glhHook )
Stop();
   }
   //安装全局钩子。
   HHOOK CKeyHook::Start()
   {
glhHook = SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);//设置键
盘钩子。
return glhHook;
}
   //卸载全局钩子。
   BOOL CKeyHook::Stop()
   {
   BOOL bResult = TRUE;
 if( glhHook )
   bResult = UnhookWindowsHookEx(glhHook);//卸载键盘钩子。
   return bResult;
   }

  (7).修改DllMain函数,代码如下:

   extern “C” int APIENTRY
   DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
   {
//如果使用lpReserved参数则删除下面这行
UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)
{
  TRACE0(”NOtePadHOOK.DLL Initializing!/n”);
   //扩展DLL仅初始化一次
  if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance))
return 0;
  new CDynLinkLibrary(ActiveKeyDLL);
      //把DLL加入动态MFC类库中
  glhInstance = hInstance;
  //插入保存DLL实例句柄
}
else if (dwReason == DLL_PROCESS_DETACH)
{
  TRACE0(”NotePadHOOK.DLL Terminating!/n”);
  //终止这个链接库前调用它
  AfxTermExtensionModule(ActiveKeyDLL);
}
return 1;
   }

  (8).编译项目ActiveKey,生成ActiveKey.DLL和ActiveKey.lib。

  接着,我们还需要创建一个外壳程序将全局钩子安装了Windows系统中,这个外壳
程序编写步骤如下:

  (1).创建一个对话框模式的应用程序,项目名为Simulate。

  (2).在主对话框中加入一个按钮,使用ClassWizard为其创建CLICK事件。

  (3).将ActiveKey项目Debug目录下的ActiveKey.DLL和ActiveKey.lib拷贝到
Simulate项目目录下。

  (4).从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标
签,在“对象/库模块”中输入

ActiveKey.lib。
5).将ActiveKey项目中的ActiveKey.h头文件加入到Simulate项目中,并在
Stdafx.h中加入#include

ActiveKey.h。

  (6).在按钮单击事件函数输入如下代码:

   void CSimulateDlg::OnButton1()
   {
// TODO: Add your control notification handler code here
if( !bSetup )
{
m_hook.Start();//激活全局钩子。
}
else
{
m_hook.Stop();//撤消全局钩子。
}
bSetup = !bSetup;

   }

  (7).编译项目,并运行程序,单击按钮激活外挂。

  (8).启动画笔程序,选择文本工具并将笔的颜色设置为红色,将鼠标放在任意位置
后,按F10键,画笔程序自动移

动鼠标并写下一个红色的大写R。图一展示了按F10键前的画笔程序的状态,图二展示了
按F10键后的画笔程序的状态。

图一:按F10前状态(001.jpg)

图二:按F10后状态(002.jpg)

五、封包技术

  通过对动作模拟技术的介绍,我们对游戏外挂有了一定程度上的认识,也学会了使
用动作模拟技术来实现简单的

动作模拟型游戏外挂的制作。这种动作模拟型游戏外挂有一定的局限性,它仅仅只能解
决使用计算机代替人力完成那

么有规律、繁琐而无聊的游戏动作。但是,随着网络游戏的盛行和复杂度的增加,很多
游戏要求将客户端动作信息及

时反馈回服务器,通过服务器对这些动作信息进行有效认证后,再向客户端发送下一步
游戏动作信息,这样动作模拟

技术将失去原有的效应。为了更好地“外挂”这些游戏,游戏外挂程序也进行了升级换
代,它们将以前针对游戏用户

界面层的模拟推进到数据通讯层,通过封包技术在客户端挡截游戏服务器发送来的游戏
控制数据包,分析数据包并修

改数据包;同时还需按照游戏数据包结构创建数据包,再模拟客户端发送给游戏服务
器,这个过程其实就是一个封包

的过程。

  封包的技术是实现第二类游戏外挂的最核心的技术。封包技术涉及的知识很广泛,
实现方法也很多,如挡截

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的

实现过程,至于如何加入外挂控制代码,还需要外挂开发人员对游戏数据包结构、内
容、加密算法等方面的仔细分析

(这个过程将是一个艰辛的过程),再生成外挂控制代码。关于数据包分析方法和技
巧,不是本文讲解的范围,如您

感兴趣可以到网上查查相关资料。

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函数,至少需要解决如下三个问题:

  ● 如何定位游戏程序中调用API函数指令代码?

  ● 如何修改游戏程序中调用API函数指令代码?

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

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

  (1) 、定位调用API函数指令代码

  我们知道,在汇编语言中使用CALL指令来调用函数或过程的,它是通过指令参数中
的函数地址而定位到相应的函

数代码的。那么,我们如果能寻找到程序代码中所有调用被挡截的API函数的CALL指令
的话,就可以将该指令中的函数

地址参数修改为替代函数的地址。虽然这是一个可行的方案,但是实现起来会很繁琐,
也不稳健。庆幸的是,Windows

系统中所使用的可执行文件(PE格式)采用了输入地址表机制,将所有在程序调用的
API函数的地址信息存放在输入地

址表中,而在程序代码CALL指令中使用的地址不是API函数的地址,而是输入地址表中
该API函数的地址项,如想使程

序代码中调用的API函数被代替掉,只用将输入地址表中该API函数的地址项内容修改即
可。具体理解输入地址表运行

机制,还需要了解一下PE格式文件结构,其中图三列出了PE格式文件的大致结构。

  图三: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”的段(输入数据段)值得我们去注
意,该段中包含着一些被称为输

入地址表(IAT,Import 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,有了这个权限您就可以

任意修改进程内存数据了。下面一段代码演示了如何将进程虚拟地址为0×0040106c处的
字节清零。

   BYTE* pData = 0×0040106c;
   MEMORY_BASIC_INFORMATION mbi_thunk;
   //查询页信息。
   VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
   //改变页保护属性为读写。
   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
   PAGE_READWRITE, &mbi_thunk.Protect);
   //清零。
   *pData = 0×00;
   //恢复页的原保护属性。
   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;//被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].Vir
tualAddress == 0)

return NULL;
    //定位第一个IAT位置。
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)

(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Virtu
alAddress));
    //根据输入库名称循环检查所有的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] == ) 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 == 0×7A )
   {
    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函
数的*作,看看信息框是不是

有所变化。同样,如此程序正在接收网络数据包,就可以实现封包功能了。

归类于: 外挂, Windows, 未分类 — nirvana @ 11:24 pm 评论(0)
关于API HOOK拦截封包原理

作者:不详  来源于:TTee.com 外挂网

http://soft.ttee.com/Article/Catalog32/95.html

我自己做的apihook,是用了陷阱式和导入表式封装在同一个类里的。源代码还没整理,而且是用delphi编写的。本人最近忙其他一个程序,加上工作忙,所以现找来网上的一篇关于apihook的文章。
本论坛很多朋友是用C++的,所以转贴了一篇C++的,原理写的蛮清楚的,用的HOOK方式是陷阱式的。
PS:大名鼎鼎的WPE就是一个优秀的API Hook,怎么样?你也可以编个WPE出来:)

===========================
利用hook截获进程的API调用  
作者:Redspider

截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。
首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下:
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // thread identifier
);
具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。
这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。

之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。

DWORD dwCurrentPID = 0;
HHOOK hOldHook = NULL;
DWORD pSend = 0;
DWORD pRecv = 0;
GETMESSAGE pGetMessage = NULL;

BYTE btNewBytes[8] = { 0×0B8, 0×0, 0×0, 0×40, 0×0, 0×0FF, 0×0E0, 0 };
DWORD dwOldBytes[3][2];

HANDLE hDebug = INVALID_HANDLE_value;

LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam )
{
DWORD dwSize;
DWORD dwPIDWatched;
HMODULE hLib;

if( dwCurrentPID == 0 )
{
dwCurrentPID = GetCurrentProcessId();
HWND hwndMainHook;
hwndMainHook = ::FindWindow( 0, “MainHook” );
dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 );
hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 );

if( dwCurrentPID == dwPIDWatched )
{
hLib = LoadLibrary( “ws2_32.dll” );
pSend = (DWORD)GetProcAddress( hLib, “send” );
pRecv = (DWORD)GetProcAddress( hLib, “recv” );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

hLib = LoadLibrary( “user32.dll” );
pGetMessage = (GETMESSAGE)GetProcAddress( hLib, “GetMessageA” );
::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

hDebug = ::CreateFile( “C:Trace.log”, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
}
}

if( hOldHook != NULL )
{
return CallNextHookEx( hOldHook, nCode, wParam, lParam );
}

return 0;
}

上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是
mov eax, 0×400000
jmp eax
这里的0×400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例:

BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax )
{
DWORD dwSize;
char szTemp[256];
BOOL r = false;

//Watch here before it’s executed.
sprintf( szTemp, “Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x //r//n”, hWnd, wMsgFilterMin, wMsgFilterMax );
::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 );
//Watch over

// restore it at first
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize );

// execute it
r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax );

// hook it again
*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage;
::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );

//Watch here after it’s executed
sprintf( szTemp, “Result of GetMessage is %d.//r//n”, r );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
if( r )
{
sprintf( szTemp, “Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X//r//nTime 0x%8.8X, X %d, Y %d//r//n”,  
lpMsg->hwnd, lpMsg->message,
lpMsg->wParam, lpMsg->lParam, lpMsg->time,
lpMsg->pt.x, lpMsg->pt.y );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );
}
strcpy( szTemp, “//r//n” );
::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 );

//Watch over

return r;
}

先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。
整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,但这个我没有试验过。

归类于: 未分类 — nirvana @ 11:23 pm 评论(0)
有关API HOOK方面的一些浅释

原作者姓名 TopLevel

介绍
本文粗略的介绍了有关API HOOK方面的一些原理,附有源代码,希望能给大家带来帮助。

读者评分 12 评分次数 4

正文
Creator        :    TopLevel
Create Date    :    2003-5-12

API HOOK(API钩子)的目的:

一看到Hook这个字眼,首先我们的脑海里浮现的就是“钩子”两词。对于“钩子”两词,看起来不是那么陌生。反正,对于我来说,至少和它还算个脸熟吧。 :P
不过你对它即使脸生,也没关系。因为在这里我们所指的Hook绝对不同于我们所熟悉的系统钩子。虽然意义不同,但有一点,就是它们目的是一样的 :拦截“控制权”(不知道,意思表达的对不对,要是对的话,就鼓鼓掌)。说白了,就是让系统执行某项动作之前,首先按照我们的意图执行,然后,再继续执行系统默认的动作。(附注:做“贼”要让别人不知道,那才叫“贼”。 ·#¥—…*·# ^_^ )
长话短说,在这里的所谓的API HOOK就是指的就是 :系统函数接口的钩子。也就是说,当系统函数进行调用时,首先进入我们指定的函数,然后再执行系统的函数。

API HOOK(API钩子)的原理:

其实,这里边的可以采用多种方法实现我们想要的功能。在这里可以就以Jmp方法的为例进行说明。Jmp方法实现简单且一目明了,很容易理解。
    其实Jmp有好几种二进制指令,我们这里是用的0xE9,汇编格式为下:

Jmp XXXX      // 其中XXXX为距离当前指令的偏移。

我们知道,系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL加载到当前的进程空间中,调用的系统函数的入口地址,可以通过GetProcAddress函数进行获取。当系统函数进行调用的时候,首先把所必要的信息保存下来(包括参数和返回地址,等一些别的信息),然后就跳转到函数的入口地址,继续执行。其实函数地址,就是系统函数“可执行代码”的开始地址。那么怎么才能让函数首先执行我们的函数呢?呵呵,应该明白了吧,把开始的那段可执行代码替换为我们自己定制的一小段可执行代码,这样系统函数调用时,不就按我们的意图乖乖行事了吗?其实,就这么简单。Very very简单。 :P
大话说出去了,可仔细一想,并没那么简单。哎,急性子没办法,“江山易改,本性难移”。

一个兄弟问 : “Top, 看你愁眉不展的,谁又把砖头当鲜花仍你了?”
Top         : “我!·#·%¥·#—(¥,”
Top心想     : “小样,不就是要干的神不知,鬼不觉的吗?不就是,不让它弹出那些可恶的带小红叉叉的对话框吗?还治不了它,K,不混了。”

要想“神不知,鬼不觉“就要做到下面最基本的两点:
    1.    把进行拦截的函数原形定义的格式和即将拦截的系统函数原形一致。也就是说,参数和返回值要一致。有且只有这样,才能使我们的拦截操作,对当前进程的健壮性,有最大程度的保障。
    2.    保持对系统函数的继续调用。如果没这一点,“神不知,鬼不觉“不仅人不相信,恐怕连鬼也不相信。比如:你拦截了MessageBox这个函数,结果你处理完自己的事情(MyMessageBox),也没继续进行系统函数的处理(MessageBox)一拍屁股,就走了,洋洋自得的样子。殊不知,用户在那傻眼,他(她)很郁闷,怎么对话框它怎么就不见了呢?一定是中木马了。哈哈,你的拦截不成功,连菜菜鸟都算不上的,都知道出问题了。这让我想起一个的广告语:”地球人都知道“。

要想不让它弹出长得不好看的带小红叉叉的对话框,呵呵。就需要SEH(Structure Exception Handle)方面的知识了,本人很菜,略知一二,考虑一般不会出现这种情况,就不打算把得砖头的本钱压在这里了。不过这方面的知识,很重要的。嘻嘻。

废话说了这么多,呵呵。该说些实际的了。下面我们就具体讲一下我们该怎么样定制我们的二进制代码,好让它跳到我们的函数中去。我们的做法是:把系统函数的入口地方的内容替换为一条Jmp指令,目的就是跳到我们的函数进行执行。而Jmp后面要求的是相对偏移,也就是我们的函数入口地址到系统函数入口地址之间的差异,再减去我们这条指令的大小。用公式表达如下:
int nDelta = UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);
Jmp nDleta;

也就是说:如果我们已经挂钩好了系统函数。那么当系统进行系统调用时,首先就碰到我们的Jmp XXXX指令,而这条指令作用就是跳到我们的函数中去执行。记住挂钩系统函数时,必须(强调的说法)把我们要替换的内容保存下来,切记,切记。如果不明白,再想想刚才所说的 2。保持对系统函数的继续调用。保存这部分内容就是为了恢复系统函数入口地址的内容,让系统函数得以继续调用。
我们采用的总体流程可概括如下:

à 保存系统函数入口 - 进入我们的函数 - 恢复系统函数入口 - 可以做一些我们想做的操作 - 调用系统函数 - 挂接系统函数 - 保存系统函数入口 à …

图例如下:
    
未挂接:

挂接后:

XXXX = 我的函数入口指针(地址)- 系统函数入口指针(地址)- sizeof (Jmp XXXX指令的大小);

API HOOK(API钩子)的应用:

屏幕取词,游戏外挂,数据包的抓取等。

具体代码如下(只演示了本进程内的API HOOK).

版权所有 @ 2002~2006 TopLevel Studio All Rights Reserved

(如有转载,劳告知一声)

特此声明:

由于在 21CN 的邮箱不太好用,toplayer@21cn.com作废,有新邮箱,另行通知。欢迎和大家多多交流。

我的新邮箱 :

toplevel@tom.com

归类于: 未分类 — nirvana @ 11:22 pm 评论(0)
用SEH技术实现API Hook

阅读本文之前,我先假设读者已经知道了 SEH 和 API Hook 的基本概念,因为我不打算在此进行扫盲工作。什么?你不懂什么叫 SEH 和 API Hook ?那……先去找点资料看看吧,到处都有哦,推荐读物:Jeffrey Richter 大牛的《Windows核心编程》。(没话可说,研究系统底层编程的葵花宝典,必备!)

另外值得补充的是,API Hook 跟一般的 Hook 是一点关系都没有的,虽然它们都是“Hook”,但是在技术上却有着天壤之别。啊……不明白?先去看看葵花宝典吧……

呵呵,废话不多说了,让我们开始吧。

经常研究 Crack 的朋友一定会知道 INT 3 这个指令。(你不知道?我倒……) 这个指令在软件调试中非常有用,因为我们可以利用它来设置特定的断点(BreakPoint),当程序遇到 INT 3 指令的时候,将会产生一个断点异常,这个异常在 Windows.inc 里面定义为 EXCEPTION_BREAKPOINT ,对应值是 080000003h 。Hoho,说了那么多,你想到什么了吗?

是的,聪明的你应该已经想到了!既然是异常,就肯定可以通过 SEH 来进行处理。于是我们可以这样做:在调用 API 之前,先设置一个断点,然后当 API 正式运行的时候,就会因为碰到 INT 3 指令而进入我们的异常处理模块,接着我们就可以在处理模块里面为所欲为了——是改变什么东西还是让它顺利通过,我没话说,看你喜欢吧……

简单地说,过程就是类似这样的:

程序遇到 INT 3 指令后,产生一个中断异常,这时 Windows 就拿着一份处理异常的活挨个问 SEH 链表上的回调函数:“你干不干?”,“不干”,“你呢?”,“我也不干”……当 Windows 终于问到我们定义好的断点异常处理函数后,他说:“让我来干好了!”,于是 Windows 就不会再问余下的人了,他把全权托给了我们的处理函数,至于我们的函数在之后做了什么手脚……呵呵,只有天知道!

明白了吗?其实在这里我们是利用了软件调试上的一个小技巧,实现了“伪 API Hook”。严格来说,这种方法不能算是真正的 API Hook ,但是由于我们可以在 SEH 回调函数中为所欲为,而系统不会发觉,所以也可以勉强算个数吧。

弄清楚原理后,剩下的就不难了。我们首先要保存目标 API 的入口地址,接着要设置一个 INT 3 指令,然后就在 SEH 的回调函数中进行地址修正等工作,最后万事倶备,只欠东风了。程序一运行,就进入了我们的 SEH 回调函数,呵呵,你爱怎么样就怎么样吧……

怎么样?一点都不难吧。罗里罗嗦地说了一大堆,可能有人会开始不耐烦了……呵,别着急,下面我就给出源代码。补充一句:本方法只是提供了一种新的思路,如果你在深入研究中发现了我的错误,或者有更好的解决方法,请给我来信啊,我的邮箱:lcother@163.net。

(注意,本技术只能在 NT/2000/XP 平台下使用)

;*********************************************************
;程序名称:用 SEH 技术实现 API Hook
;适用系统:Win NT/2000/XP
;作者:罗聪
;日期:2002-11-22
;出处:http://www.LuoCong.com(老罗的缤纷天地)
;注意事项:如欲转载,请保持本程序的完整,并注明:
;转载自“老罗的缤纷天地”(http://www.LuoCong.com)
;*********************************************************

.386
.model flat, stdcall
option casemap:none

include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib

WndProc         proto :DWORD, :DWORD, :DWORD, :DWORD
Error_Handler   proto :DWORD, :DWORD, :DWORD, :DWORD
SetHook         proto

.const
IDI_LC                  equ 1
IDC_CHECKBUTTON_HOOK    equ 3000
IDC_BUTTON_ABOUT        equ 3001
IDC_BUTTON_EXIT         equ 3002

.data
szDlgName               db  “lc_dialog”, 0
szMsgAbout              db  “-= SEH for API Hook =-”, 13, 10, 13, 10,/
                            “作者:罗聪(lcother@163.net)”, 13, 10, 13, 10,/
                            “老罗的缤纷天地”, 13, 10,/
                            “http://www.LuoCong.com“, 13, 10, 0
szMyText                db  13, 10, 13, 10, “(哈哈,看到有什么不同了吗?)”, 0
szMsgHooked             db  “MessageBoxIndirectA() has been hooked!”,/
                            13, 10, 13, 10,/
                            “即将改变原来的 MessageBoxIndirectA() 的参数,”, 13, 10,/
                            “请注意后面的对话框跟没有 Hook 之前有什么不同……”, 0
szCaption               db  “SEH for API Hook by LC”, 0
szLibUser               db  “user32″, 0
szProcMsgBoxInd         db  “MessageBoxIndirectA”, 0
dwAddress               dd  0
dwOldProtect            dd  0
bOldByte                db  0
dwRetAddr               dd  0

.data?
hInstance               HINSTANCE       ?
mbp                     MSGBOXPARAMS   
szText                  db  1024 dup(?)

.code
main:
    ; 设置 SEH 链:
    assume  fs:nothing
    push    offset Error_Handler
    push    fs:[0]
    mov     fs:[0], esp

invoke  GetModuleHandle, NULL
    mov     hInstance, eax
    invoke  DialogBoxParam, hInstance, offset szDlgName, 0, WndProc, 0

; 恢复原来的 SEH 链:
    pop     fs:[0]
    pop     eax
    invoke  ExitProcess, 0

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg == WM_CLOSE
        invoke EndDialog, hWnd, 0

.elseif uMsg == WM_INITDIALOG
        mov eax, hWnd
        mov [mbp.hwndOwner], eax
        invoke LoadIcon, hInstance, IDI_LC
        invoke SendMessage, hWnd, WM_SETICON, ICON_SMALL, eax
        ; 储存 API 的原入口地址:
        invoke GetModuleHandle, addr szLibUser
        invoke GetProcAddress, eax, addr szProcMsgBoxInd
        mov [dwAddress], eax
        ; 保存原对话框的输出文字:
        invoke lstrcpy, addr szText, addr szMsgAbout

.elseif uMsg == WM_COMMAND
        mov eax, wParam
        mov edx, eax
        shr edx, 16
        movzx eax, ax
        .if edx == BN_CLICKED
            .if eax == IDC_BUTTON_EXIT || eax == IDCANCEL
                invoke EndDialog, hWnd, NULL

.elseif eax == IDC_BUTTON_ABOUT || eax == IDOK
                mov [mbp.cbSize], sizeof mbp
                mov eax, hInstance
                mov [mbp.hInstance], eax
                mov [mbp.lpszText], offset szMsgAbout
                mov [mbp.lpszCaption], offset szCaption
                mov [mbp.dwStyle], MB_OK or MB_APPLMODAL or MB_USERICON
                mov [mbp.lpszIcon], IDI_LC
                invoke MessageBoxIndirect, addr mbp

.elseif eax == IDC_CHECKBUTTON_HOOK
                ; 把内存保护设置成 可读/可写/可执行:
                invoke VirtualProtect, [dwAddress], 1, PAGE_EXECUTE_READWRITE, addr dwOldProtect
                invoke IsDlgButtonChecked, hWnd, IDC_CHECKBUTTON_HOOK
                mov edx, [dwAddress]
                test eax, eax
                .if zero?                                           ; uninstall hook
                    mov cl, [bOldByte]                              ; bOldByte = API 原入口地址
                    mov byte ptr [edx], cl                          ; 恢复 API 的原入口地址
                    invoke lstrcpy, addr szMsgAbout, addr szText    ; 恢复原对话框的输出文字:
                .else                                               ; re-install hook
                    mov cl, byte ptr [edx]                          ; byte ptr [edx] = API 原入口地址
                    mov byte ptr [edx], 0CCh                        ; 断点异常(INT 3 指令)
                    mov [bOldByte], cl                              ; 储存 API 的原入口地址
                    invoke lstrcat, addr szMsgAbout, addr szMyText  ; 改变原对话框的输出文字:
                .endif

.endif
        .endif
    .else
        mov eax, FALSE
        ret
    .endif
    mov eax, TRUE
    ret
WndProc endp

;****************************************
; 函数功能:处理异常错误
;****************************************
Error_Handler proc uses ecx lpExceptRecord:DWORD, lpFrame:DWORD, lpContext:DWORD, lpDispatch:DWORD
    ; 输出 “API hooked”:
    invoke  MessageBox, [mbp.hwndOwner], addr szMsgHooked, addr szCaption,/
            MB_OK or MB_ICONINFORMATION

; 储存并改变 SetHook 函数的返回值:(经过修正)
    ; (想不明白?呵呵,用调试器跟踪一下吧,我也说不清楚,只能意会不能言传……)
    mov eax, [lpContext]
    mov eax, [eax][CONTEXT.regEsp]
    mov ecx, [eax]
    mov [eax], offset SetHook
    mov [dwRetAddr], ecx

; 把 API 原入口地址写回去,以便继续运行原 API:
    ; (跟踪一下吧,我实在是不知道怎么才能说得清楚……)
    mov eax, [dwAddress]
    mov cl, [bOldByte]
    mov byte ptr [eax], cl

; 继续下一个 Execution:
    mov eax, ExceptionContinueExecution
    ret
Error_Handler endp

;****************************************
; 函数功能:设置 API Hook
;****************************************
SetHook proc uses ecx
    mov eax, [dwAddress]
    mov cl, [eax]
    mov byte ptr [eax], 0CCh    ; 断点异常(INT 3 指令)
    mov [bOldByte], cl
    jmp [dwRetAddr]             ; 跳回经过 Hook 之后的 API 的返回地址(很重要!)
SetHook endp

end main
;********************   over    ********************
;by LC

它的资源文件:

#include “resource.h”

#define IDI_LC              1
#define IDC_CHECKBOX_HOOK   3000
#define IDC_BUTTON_ABOUT    3001
#define IDC_BUTTON_EXIT     3002
#define IDC_STATIC          -1

IDI_LC  ICON    “lc.ico”

LC_DIALOG DIALOGEX 10, 10, 200, 50
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION “SEH for API Hook by LC, 2002-11-22″
FONT 8, “MS Sans Serif”
BEGIN
    AUTOCHECKBOX    “&Hook MessageBoxIndirectA”, IDC_CHECKBOX_HOOK, 5, 5, 190, 12
    PUSHBUTTON      “关于(&A)”, IDC_BUTTON_ABOUT, 5, 30, 90, 14, BS_FLAT | BS_CENTER
    PUSHBUTTON      “退出(&X)”, IDC_BUTTON_EXIT, 105, 30, 90, 14, BS_FLAT | BS_CENTER
END

没啥特别的,仔细一想就明白了。

归类于: 未分类 — nirvana @ 11:20 pm 评论(0)
外挂制作教程


随着网络游戏的日益火爆,很多玩家都投身到游戏中。目前很多玩家都依赖于一些游戏的外挂程序来进行游戏。那么做一个游戏的外挂程序是否是一件很困难的事呢?回答是”否”,诚然编写一些程序是需要一些基本知识的,所以我们想以最简单的语言来给你讲授编写外挂程序的一些技巧,一些方法,并提供给你一些基本的辅助工具,即使你是一个菜鸟,看了我们的教程,并技巧地使用我们提供给你的工具,你完全能够编写出一个完全属于你自己的外挂。在本教程内,我们提供了金庸群侠传,以及网络三国这两个游戏的修改实际例子,因为这两款游戏都是对封包进行过加密运算的,如果你对这两个游戏的修改有了一定的了解后,相信你在其他游戏中也能非常好地做出属于自己的外挂。我们提供了金庸打增援20个NPC和网络三国在PK中自动吃药,自动发镖这两个实际的例子,让你上手更容易。我们会在教程内讲授给你怎么去破解封包的加密算法,怎么利用我们提供给你工具来伪造和发送封包。本教程除了文字教程外,我们还会提供金庸群侠和三国的外挂程序,另外还提供6个外挂制作工具,以供你使用。希望在以后的游戏中,每一个玩家都能够在游戏中成长起来,不但游戏玩的出色,修改游戏也同样出色,做一个真正的游戏DIY。
要想在修改游戏中做到百战百胜,是需要相当丰富的计算机知识的。有很多计算机高手就是从玩游戏,修改游戏中,逐步对计算机产生浓厚的兴趣,逐步成长起来的。不要在羡慕别人能够做到的,因为别人能够做的你也能够!我相信你们看了本教程后,会对游戏有一个全新的认识,呵呵,因为我是个好老师!(别拿鸡蛋砸我呀,救命啊!#¥%……*)
  不过要想从修改游戏中学到知识,增加自己的计算机水平,可不能只是靠修改游戏呀! 要知道,修改游戏只是一个验证你对你所了解的某些计算机知识的理解程度的场所,只能给你一些发现问题、解决问题的机会,只能起到帮助你提高学习计算机的兴趣的作用,而决不是学习计算机的捷径。

一:什么叫外挂?
现在的网络游戏多是基于Internet上客户/服务器模式,服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家可以通过运行客户端程序同时登录到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操作。那么既然在网络游戏中有了服务器这个概念,我们以前传统的修改游戏方法就显得无能为力了。记得我们在单机版的游戏中,随心所欲地通过内存搜索来修改角色的各种属性,这在网络游戏中就没有任何用处了。因为我们在网络游戏中所扮演角色的各种属性及各种重要资料都存放在服务器上,在我们自己机器上(客户端)只是显示角色的状态,所以通过修改客户端内存里有关角色的各种属性是不切实际的。那么是否我们就没有办法在网络游戏中达到我们修改的目的?回答是”否”。我们知道Internet客户/服务器模式的通讯一般采用TCP/IP通信协议,数据交换是通过IP数据包的传输来实现的,一般来说我们客户端向服务器发出某些请求,比如移动、战斗等指令都是通过封包的形式和服务器交换数据。那么我们把本地发出消息称为SEND,意思就是发送数据,服务器收到我们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,比如,移动的坐标,战斗的类型。那么我们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来我们要做的工作就是分析客户端和服务器之间往来的数据(也就是封包),这样我们就可以提取到对我们有用的数据进行修改,然后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就可以实现我们修改游戏的目的了。
目前除了修改游戏封包来实现修改游戏的目的,我们也可以修改客户端的有关程序来达到我们的要求。我们知道目前各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中所有玩家的状况几乎是不可能的,所以有一些运算还是要依靠我们客户端来完成,这样又给了我们修改游戏提供了一些便利。比如我们可以通过将客户端程序脱壳来发现一些程序的判断分支,通过跟踪调试我们可以把一些对我们不利的判断去掉,以此来满足我们修改游戏的需求。
在下几个章节中,我们将给大家讲述封包的概念,和修改跟踪客户端的有关知识。大家准备好了吗?

游戏数据格式和存储:

在进行我们的工作之前,我们需要掌握一些关于计算机中储存数据方式的知识和游戏中储存数据的特点。本章节是提供给菜鸟级的玩家看的,如果你是高手就可以跳过了,呵呵!
  如果,你想成为无坚不摧的剑客,那么,这些东西就会花掉你一些时间;如果,你只想作个江湖的游客的话,那么这些东西,了解与否无关紧要。是作剑客,还是作游客,你选择吧!

现在我们开始!首先,你要知道游戏中储存数据的几种格式,这几种格式是:字节(BYTE)、字(WORD)和双字(DOUBLE WORD),或者说是8位、16位和32位储存方式。字节也就是8位方式能储存0~255的数字;字或说是16位储存方式能储存0~65535的数;双字即32位方式能储存0~4294967295的数。

为何要了解这些知识呢?在游戏中各种参数的最大值是不同的,有些可能100左右就够了,比如,金庸群侠传中的角色的等级、随机遇敌个数等等。而有些却需要大于255甚至大于65535,象金庸群侠传中角色的金钱值可达到数百万。所以,在游戏中各种不同的数据的类型是不一样的。在我们修改游戏时需要寻找准备修改的数据的封包,在这种时候,正确判断数据的类型是迅速找到正确地址的重要条件。

  在计算机中数据以字节为基本的储存单位,每个字节被赋予一个编号,以确定各自的位置。这个编号我们就称为地址。

在需要用到字或双字时,计算机用连续的两个字节来组成一个字,连续的两个字组成一个双字。而一个字或双字的地址就是它们的低位字节的地址。
现在我们常用的Windows 9x操作系统中,地址是用一个32位的二进制数表示的。而在平时我们用到内存地址时,总是用一个8位的16进制数来表示它。

二进制和十六进制又是怎样一回事呢?

  简单说来,二进制数就是一种只有0和1两个数码,每满2则进一位的计数进位法。同样,16进制就是每满十六就进一位的计数进位法。16进制有0–F十六个数字,它为表示十到十五的数字采用了A、B、C、D、E、F六个数字,它们和十进制的对应关系是:A对应于10,B对应于11,C对应于12,D对应于13,E对应于14,F对应于15。而且,16进制数和二进制数间有一个简单的对应关系,那就是;四位二进制数相当于一位16进制数。比如,一个四位的二进制数1111就相当于16进制的F,1010就相当于A。

了解这些基础知识对修改游戏有着很大的帮助,下面我就要谈到这个问题。由于在计算机中数据是以二进制的方式储存的,同时16进制数和二进制间的转换关系十分简单,所以大部分的修改工具在显示计算机中的数据时会显示16进制的代码,而且在你修改时也需要输入16进制的数字。你清楚了吧?

  在游戏中看到的数据可都是十进制的,在要寻找并修改参数的值时,可以使用Windows提供的计算器来进行十进制和16进制的换算,我们可以在开始菜单里的程序组中的附件中找到它。

  现在要了解的知识也差不多了!不过,有个问题在游戏修改中是需要注意的。在计算机中数据的储存方式一般是低位数储存在低位字节,高位数储存在高位字节。比如,十进制数41715转换为16进制的数为A2F3,但在计算机中这个数被存为F3A2。

看了以上内容大家对数据的存贮和数据的对应关系都了解了吗? 好了,接下来我们要告诉大家在游戏中,封包到底是怎么一回事了,来!大家把袖口卷起来,让我们来干活吧!

二:什么是封包?
怎么截获一个游戏的封包?
怎么去检查游戏服务器的ip地址和端口号?
Internet用户使用的各种信息服务,其通讯的信息最终均可以归结为以IP包为单位的信息传送,IP包除了包括要传送的数据信息外,还包含有信息要发送到的目的IP地址、信息发送的源IP地址、以及一些相关的控制信息。当一台路由器收到一个IP数据包时,它将根据数据包中的目的IP地址项查找路由表,根据查找的结果将此IP数据包送往对应端口。下一台IP路由器收到此数据包后继续转发,直至发到目的地。路由器之间可以通过路由协议来进行路由信息的交换,从而更新路由表。

那么我们所关心的内容只是IP包中的数据信息,我们可以使用许多监听网络的工具来截获客户端与服务器之间的交换数据,下面就向你介绍其中的一种工具:WPE。

WPE使用方法:
执行WPE会有下列几项功能可选择:

SELECT GAME
选择目前在记忆体中您想拦截的程式,您只需双击该程式名称即可。

TRACE
追踪功能。用来追踪撷取程式送收的封包。
WPE必须先完成点选欲追踪的程式名称,才可以使用此项目。
按下Play键开始撷取程式收送的封包。
您可以随时按下 | | 暂停追踪,想继续时请再按下 | | 。
按下正方形可以停止撷取封包并且显示所有已撷取封包内容。
若您没按下正方形停止键,追踪的动作将依照OPTION里的设定值自动停止。
如果您没有撷取到资料,试试将OPTION里调整为Winsock Version 2。
WPE 及 Trainers 是设定在显示至少16 bits 颜色下才可执行。

FILTER
过滤功能。用来分析所撷取到的封包,并且予以修改。

SEND PACKET
送出封包功能。能够让您送出假造的封包。

TRAINER MAKER
制作修改器。

OPTIONS
设定功能。让您调整WPE的一些设定值。

FILTER的详细教学

- 当FILTER在启动状态时 ,ON的按钮会呈现红色。
- 当您启动FILTER时,您随时可以关闭这个视窗。FILTER将会保留在原来的状态,直到您再按一次 on / off 钮。
- 只有FILTER启用钮在OFF的状态下,才可以勾选Filter前的方框来编辑修改。
- 当您想编辑某个Filter,只要双击该Filter的名字即可。

NORMAL MODE:

范例:

当您在 Street Fighter Online ﹝快打旋风线上版﹞游戏中,您使用了两次火球而且击中了对方,这时您会撷取到以下的封包:
SEND-> 0000 08 14 21 06 01 04
SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00
SEND-> 0000 03 84 11 09 11 09
SEND-> 0000 0A 09 C1 10 00 00 FF 52 44
SEND-> 0000 0A 09 C1 10 00 00 66 52 44

您的第一个火球让对方减了16滴﹝16 = 10h﹞的生命值,
而您观察到第4跟第5个封包的位置4有10h的值出现,应该就是这里了。

您观察10h前的0A 09 C1在两个封包中都没改变,可见得这3个数值是发出火球的关键。

因此您将0A 09 C1 10填在搜寻列﹝SEARCH﹞,然后在修改列﹝MODIFY﹞的位置4填上FF。如此一来,当您再度发出火球时,FF会取代之前的10,也就是攻击力为255的火球了!

ADVANCED MODE:

范例:
当您在一个游戏中,您不想要用真实姓名,您想用修改过的假名传送给对方。在您使用TRACE后,您会发现有些封包里面有您的名字出现。假设您的名字是Shadow,换算成16进位则是﹝53 68 61 64 6F 77﹞;而您打算用moon﹝6D 6F 6F 6E 20 20﹞来取代他。
1) SEND-> 0000 08 14 21 06 01 04
2) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05
3) SEND-> 0000 03 84 11 09 11 09
4) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11
5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44

但是您仔细看,您的名字在每个封包中并不是出现在相同的位置上

- 在第2个封包里,名字是出现在第4个位置上
- 在第4个封包里,名字是出现在第6个位置上

在这种情况下,您就需要使用ADVANCED MODE
- 您在搜寻列﹝SEARCH﹞填上:53 68 61 64 6F 77 ﹝请务必从位置1开始填﹞
- 您想要从原来名字Shadow的第一个字母开始置换新名字,因此您要选择从数值被发现的位置开始替代连续数值﹝ the position of the chain found﹞。
- 现在,在修改列﹝MODIFY﹞000的位置填上:6D 6F 6F 6E 20 20 ﹝此为相对应位置,也就是从原来搜寻栏的+001位置开始递换﹞
- 如果您想从封包的第一个位置就修改数值,请选择﹝ the beginning of the packet﹞

了解一点TCP/IP协议常识的人都知道,互联网是将信息数据打包之后再传送出去的。每个数据包分为头部信息和数据信息两部分。头部信息包括数据包的发送地址和到达地址等。数据信息包括我们在游戏中相关操作的各项信息。那么在做截获封包的过程之前我们先要知道游戏服务器的IP地址和端口号等各种信息,实际上最简单的是看看我们游戏目录下,是否有一个SERVER.INI的配置文件,这个文件里你可以查看到个游戏服务器的IP地址,比如金庸群侠传就是如此,那么除了这个我们还可以在DOS下使用NETSTAT这个命令,

NETSTAT命令的功能是显示网络连接、路由表和网络接口信息,可以让用户得知目前都有哪些网络连接正在运作。或者你可以使用木马客星等工具来查看网络连接。工具是很多的,看你喜欢用哪一种了。

NETSTAT命令的一般格式为:
NETSTAT [选项]

命令中各选项的含义如下:
-a 显示所有socket,包括正在监听的。
-c 每隔1秒就重新显示一遍,直到用户中断它。
-i 显示所有网络接口的信息。
-n 以网络IP地址代替名称,显示出网络连接情形。
-r 显示核心路由表,格式同”route -e”。
-t 显示TCP协议的连接情况。
-u 显示UDP协议的连接情况。
-v 显示正在进行的工作。
三:怎么来分析我们截获的封包?
首先我们将WPE截获的封包保存为文本文件,然后打开它,这时会看到如下的数据(这里我们以金庸群侠传里PK店小二客户端发送的数据为例来讲解):

第一个文件:
SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1B
SEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9B
SEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1A
SEND-> 0000 E6 56 1B C0 68 12 12 12 5A
SEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12
SEND-> 0000 E6 56 17 C9 12

第二个文件:
SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7E
SEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6D
SEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3
SEND-> 0000 83 33 7E A5 21 77 77 77 3F
SEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77
SEND-> 0000 83 33 72 AC 77

我们发现两次PK店小二的数据格式一样,但是内容却不相同,我们是PK的同一个NPC,为什么会不同呢?
原来金庸群侠传的封包是经过了加密运算才在网路上传输的,那么我们面临的问题就是如何将密文解密成明文再分析了。

因为一般的数据包加密都是异或运算,所以这里先讲一下什么是异或。
简单的说,异或就是”相同为0,不同为1″(这是针对二进制按位来讲的),举个例子,0001和0010异或,我们按位对比,得到异或结果是0011,计算的方法是:0001的第4位为0,0010的第4位为0,它们相同,则异或结果的第4位按照”相同为0,不同为1″的原则得到0,0001的第3位为0,0010的第3位为0,则异或结果的第3位得到0,0001的第2位为0,0010的第2位为1,则异或结果的第2位得到1,0001的第1位为1,0010的第1位为0,则异或结果的第1位得到1,组合起来就是0011。异或运算今后会遇到很多,大家可以先熟悉熟悉,熟练了对分析很有帮助的。

下面我们继续看看上面的两个文件,按照常理,数据包的数据不会全部都有值的,游戏开发时会预留一些字节空间来便于日后的扩充,也就是说数据包里会存在一些”00″的字节,观察上面的文件,我们会发现文件一里很多”12″,文件二里很多”77″,那么这是不是代表我们说的”00″呢?推理到这里,我们就开始行动吧!

我们把文件一与”12″异或,文件二与”77″异或,当然用手算很费事,我们使用”M2M 1.0 加密封包分析工具”来计算就方便多了。得到下面的结果:

第一个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89
2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 08
3 SEND-> 0000 F4 44 09 D2 7A 00 00 00 48
4 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00
5 SEND-> 0000 F4 44 05 DB 00

第二个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A
2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 84
3 SEND-> 0000 F4 44 09 D2 56 00 00 00 48
4 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 00
5 SEND-> 0000 F4 44 05 DB 00

哈,这一下两个文件大部分都一样啦,说明我们的推理是正确的,上面就是我们需要的明文!

接下来就是搞清楚一些关键的字节所代表的含义,这就需要截获大量的数据来分析。

首先我们会发现每个数据包都是”F4 44″开头,第3个字节是变化的,但是变化很有规律。我们来看看各个包的长度,发现什么没有?对了,第3个字节就是包的长度!
通过截获大量的数据包,我们判断第4个字节代表指令,也就是说客户端告诉服务器进行的是什么操作。例如向服务器请求战斗指令为”30″,战斗中移动指令为”D4″等。
接下来,我们就需要分析一下上面第一个包”F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89″,在这个包里包含什么信息呢?应该有通知服务器你PK的哪个NPC吧,我们就先来找找这个店小二的代码在什么地方。
我们再PK一个小喽罗(就是大理客栈外的那个咯):
SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0
我们根据常理分析,游戏里的NPC种类虽然不会超过65535(FFFF),但开发时不会把自己限制在字的范围,那样不利于游戏的扩充,所以我们在双字里看看。通过”店小二”和”小喽罗”两个包的对比,我们把目标放在”6C 79 F6 05″和”CF 26 00 00″上。(对比一下很容易的,但你不能太迟钝咯,呵呵)我们再看看后面的包,在后面的包里应该还会出现NPC的代码,比如移动的包,游戏允许观战,服务器必然需要知道NPC的移动坐标,再广播给观战的其他玩家。在后面第4个包”SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00″里我们又看到了”6C 79 F6 05″,初步断定店小二的代码就是它了!
(这分析里边包含了很多工作的,大家可以用WPE截下数据来自己分析分析)

第一个包的分析暂时就到这里(里面还有的信息我们暂时不需要完全清楚了)

我们看看第4个包”SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00″,再截获PK黄狗的包,(狗会出来2只哦)看看包的格式:
SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00
SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00

根据上面的分析,黄狗的代码为”4B 7D F6 05″(100040011),不过两只黄狗服务器怎样分辨呢?看看”EB 03 F8 05″(100140011),是上一个代码加上100000,呵呵,这样服务器就可以认出两只黄狗了。我们再通过野外遇敌截获的数据包来证实,果然如此。

那么,这个包的格式应该比较清楚了:第3个字节为包的长度,”DA”为指令,第5个字节为NPC个数,从第7个字节开始的10个字节代表一个NPC的信息,多一个NPC就多10个字节来表示。

大家如果玩过网金,必然知道随机遇敌有时会出现增援,我们就利用游戏这个增援来让每次战斗都会出现增援的NPC吧。

通过在战斗中出现增援截获的数据包,我们会发现服务器端发送了这样一个包:
F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00
第5-第8个字节为增援NPC的代码(这里我们就简单的以黄狗的代码来举例)。
那么,我们就利用单机代理技术来同时欺骗客户端和服务器吧!

好了,呼叫NPC的工作到这里算是完成了一小半,接下来的事情,怎样修改封包和发送封包,我们下节继续讲解吧。

四:怎么冒充”客户端”向”服务器”发我们需要的封包?
这里我们需要使用一个工具,它位于客户端和服务器端之间,它的工作就是进行数据包的接收和转发,这个工具我们称为代理。
如果代理的工作单纯就是接收和转发的话,这就毫无意义了,但是请注意:所有的数据包都要通过它来传输,这里的意义就重大了。我们可以分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造我们需要的封包来发送。

下面我们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。
通过我们上节的分析,我们已经知道了打多个NPC的封包格式,那么我们就动手吧!

首先我们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,我们找”F4 44 1F 30″这个特征,这是不会改变的,当然是要解密后来查找哦。
找到后,表示客户端在向服务器请求战斗,我们不动这个包,转发。
继续向下查找,这时需要查找的特征码不太好办,我们先查找”DA”,这是客户端发送NPC信息的数据包的指令,那么可能其他包也有”DA”,没关系,我们看前3个字节有没有”F4 44″就行了。找到后,我们的工作就开始了!

我们确定要打的NPC数量。这个数量不能很大,原因在于网金的封包长度用一个字节表示,那么一个包可以有255个字节,我们上面分析过,增加一个NPC要增加10个字节,所以大家算算就知道,打20个NPC比较合适。

然后我们要把客户端原来的NPC代码分析计算出来,因为增加的NPC代码要加上100000哦。再把我们增加的NPC代码计算出来,并且组合成新的封包,注意代表包长度的字节要修改啊,然后转发到服务器,这一步在编写程序的时候要注意算法,不要造成较大延迟。

上面我们欺骗服务器端完成了,欺骗客户端就简单了,^-^

发送了上面的封包后,我们根据新增NPC代码构造封包马上发给客户端,格式就是”F4 44 12 E9 NPC代码 02 00 00 03 00 00 00 00 00 00″,把每个新增的NPC都构造这样一个包,按顺序连在一起发送给客户端,客户端也就被我们骗过了,很简单吧。

以后战斗中其他的事我们就不管了,尽情地开打吧,呵呵。

上面讲的需要一定的编程基础,但是不难,即使你不会编程,相信你继续看下去就会有收获了
六:如果单机代理被封,我们怎么利用底层的技术来接管游戏的发包?
在WINDOWS系统中,网络通讯的任务是由一个叫WSOCK32.DLL(在SYSTEM目录下)来完成的,每当游戏被运行时,他都会自动的去调用这个动态连接库,因为在WINDOWS系统中对于文件的搜索顺序是 程序目录>系统目录>路径中设置的目录,所以我们就有机会替换掉系统的WSOCK32.DLL使的游戏调用我们的WSOCK32.DLL,这样我们就有了对于游戏封包绝对的控制权,有人问:”我们应该怎么做呢?”,我们只要自己编写一个WSOCK32.DLL放到游戏的目录下,就OK了,当然让我们完全自己去编写一个WSOCK32.DLL是不太现实的,因为本身网络通讯要处理很多更底层的东西,比如说从网卡读取BIT流,所以我们选择由我们的WSOCK32.DLL去调用系统的WSOCK32.DLL来完成这个功能。

WSOCK32.DLL有很多的输出函数,函数如下:
__WSAFDIsSet
accept
AcceptEx
Arecv
Asend
bind
closesocket
closesockinfo
connect
dn_expand
EnumProtocolsA
EnumProtocolsW
GetAcceptExSockaddrs
GetAddressByNameA
GetAddressByNameW
gethostbyaddr
gethostbyname
gethostname
GetNameByTypeA
GetNameByTypeW
getnetbyname
getpeername
getprotobyname
getprotobynumber
getservbyname
getservbyport
GetServiceA
GetServiceW
getsockname
getsockopt
GetTypeByNameA
GetTypeByNameW
htonl
htons
inet_addr
inet_network
inet_ntoa
ioctlsocket
listen
MigrateWinsockConfiguration
NPLoadNameSpaces
NSPStartup
ntohl
ntohs
rcmd
recv
recv
rexec
rresvport
s_perror
select
send
sendto
sethostname
SetServiceA
SetServiceW
setsockopt
shutdown
socket
TransmitFile
WEP
WSAAsyncGetHostByAddr
WSAAsyncGetHostByName
WSAAsyncGetProtoByName
WSAAsyncGetProtoByNumber
WSAAsyncGetServByName
WSAAsyncGetServByPort
WSAAsyncSelect
WSACancelAsyncRequest
WSACancelBlockingCall
WSACleanup
WSAGetLastError
WSAIsBlocking
WSApSetPostRoutine
WSARecvEx
WSASetBlockingHook
WSASetLastError
WSAStartup
WSAUnhookBlockingHook
WsControl
WSHEnumProtocols

在这里,不是所有的函数都要修改,因为我们只关心发送和接收的封包,所以我们只要修改send 和recv两个函数,前者是发送封包的后者是接收封包的,我们在这两个函数的处理中加入我们自己的代码,来完成封包的辨认,修改以及转发等功能。

六:如果单机代理被封,我们怎么利用底层的技术来接管游戏的发包?
在WINDOWS系统中,网络通讯的任务是由一个叫WSOCK32.DLL(在SYSTEM目录下)来完成的,每当游戏被运行时,他都会自动的去调用这个动态连接库,因为在WINDOWS系统中对于文件的搜索顺序是 程序目录>系统目录>路径中设置的目录,所以我们就有机会替换掉系统的WSOCK32.DLL使的游戏调用我们的WSOCK32.DLL,这样我们就有了对于游戏封包绝对的控制权,有人问:”我们应该怎么做呢?”,我们只要自己编写一个WSOCK32.DLL放到游戏的目录下,就OK了,当然让我们完全自己去编写一个WSOCK32.DLL是不太现实的,因为本身网络通讯要处理很多更底层的东西,比如说从网卡读取BIT流,所以我们选择由我们的WSOCK32.DLL去调用系统的WSOCK32.DLL来完成这个功能。

WSOCK32.DLL有很多的输出函数,函数如下:
__WSAFDIsSet
accept
AcceptEx
Arecv
Asend
bind
closesocket
closesockinfo
connect
dn_expand
EnumProtocolsA
EnumProtocolsW
GetAcceptExSockaddrs
GetAddressByNameA
GetAddressByNameW
gethostbyaddr
gethostbyname
gethostname
GetNameByTypeA
GetNameByTypeW
getnetbyname
getpeername
getprotobyname
getprotobynumber
getservbyname
getservbyport
GetServiceA
GetServiceW
getsockname
getsockopt
GetTypeByNameA
GetTypeByNameW
htonl
htons
inet_addr
inet_network
inet_ntoa
ioctlsocket
listen
MigrateWinsockConfiguration
NPLoadNameSpaces
NSPStartup
ntohl
ntohs
rcmd
recv
recv
rexec
rresvport
s_perror
select
send
sendto
sethostname
SetServiceA
SetServiceW
setsockopt
shutdown
socket
TransmitFile
WEP
WSAAsyncGetHostByAddr
WSAAsyncGetHostByName
WSAAsyncGetProtoByName
WSAAsyncGetProtoByNumber
WSAAsyncGetServByName
WSAAsyncGetServByPort
WSAAsyncSelect
WSACancelAsyncRequest
WSACancelBlockingCall
WSACleanup
WSAGetLastError
WSAIsBlocking
WSApSetPostRoutine
WSARecvEx
WSASetBlockingHook
WSASetLastError
WSAStartup
WSAUnhookBlockingHook
WsControl
WSHEnumProtocols

在这里,不是所有的函数都要修改,因为我们只关心发送和接收的封包,所以我们只要修改send 和recv两个函数,前者是发送封包的后者是接收封包的,我们在这两个函数的处理中加入我们自己的代码,来完成封包的辨认,修改以及转发等功能。
七:怎么来分析客户端的有关资料?
自己作外挂,大多时候要分析封包,不过因为有的功能是由客户端来辨别的,所以分析客户端的程序同样也很重要,分析客户端首先要求你能看懂汇编指令(只要”看懂”,要求很低的),其次是要能够熟练的运用一些工具,然后能剩下的也就是运气和游戏公司的漏洞了。(哈,不是每次都能成功的啊)下边我分步教给大家。

第一章 8086汇编指令

注:AX,BX,CX…,EAX,EBX,ECX…这些都是CPU用来存储数据的地方。

一、数据传输指令
作用:它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.
1. 通用数据传送指令.
MOV 传送字或字节.
MOVSX 先符号扩展,再传送.
MOVZX 先零扩展,再传送.
PUSH 把字压入堆栈.
POP 把字弹出堆栈.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
BSWAP 交换32位寄存器里字节的顺序
XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
XADD 先交换再累加.( 结果在第一个操作数里 )
XLAT 字节查表转换.
BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即 0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL )

2. 输入输出端口传送指令.
IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )
输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,其范围是 0-65535.

3. 目的地址传送指令.
LEA 装入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 传送目标指针,把指针内容装入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 传送目标指针,把指针内容装入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 传送目标指针,把指针内容装入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 传送目标指针,把指针内容装入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 传送目标指针,把指针内容装入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.

4. 标志传送指令.
LAHF 标志寄存器传送,把标志装入AH.
SAHF 标志寄存器传送,把AH内容装入标志寄存器.
PUSHF 标志入栈.
POPF 标志出栈.
PUSHD 32位标志入栈.
POPD 32位标志出栈.

二、算术运算指令
ADD 加法.
ADC 带进位加法.
INC 加 1.
AAA 加法的ASCII码调整.
DAA 加法的十进制调整.
SUB 减法.
SBB 带借位减法.
DEC 减 1.
NEC 求反(以 0 减之).
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果).
AAS 减法的ASCII码调整.
DAS 减法的十进制调整.
MUL 无符号乘法.
IMUL 整数乘法.
以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
AAM 乘法的ASCII码调整.
DIV 无符号除法.
IDIV 整数除法.
以上两条,结果回送:
商回送AL,余数回送AH, (字节运算);
或 商回送AX,余数回送DX, (字运算).
AAD 除法的ASCII码调整.
CBW 字节转换为字. (把AL中字节的符号扩展到AH中去)
CWD 字转换为双字. (把AX中的字的符号扩展到DX中去)
CWDE 字转换为双字. (把AX中的字符号扩展到EAX中去)
CDQ 双字扩展. (把EAX中的字的符号扩展到EDX中去)

三、逻辑运算指令
AND 与运算.
OR 或运算.
XOR 异或运算.
NOT 取反.
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.
以上八种移位指令,其移位次数可达255次.
移位一次时, 可直接用操作码. 如 SHL AX,1.
移位>1次时, 则由寄存器CL给出移位次数.
如 MOV CL,04
SHL AX,CL

四、串指令
DS:SI 源串段寄存器 :源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
Z标志 用来控制扫描或比较操作的结束.
MOVS 串传送.
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.
( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.
把AL或AX的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.
把源串中的元素(字或字节)逐一装入AL或AX中.
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串.
是LODS的逆过程.
REP 当CX/ECX0时重复.
REPE/REPZ 当ZF=1或比较结果相等,且CX/ECX0时重复.
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX0时重复.
REPC 当CF=1且CX/ECX0时重复.
REPNC 当CF=0且CX/ECX0时重复.

五、程序转移指令
1>无条件转移指令 (长转移)
JMP 无条件转移指令
CALL 过程调用
RET/RETF过程返回.
2>条件转移指令 (短转移,-128到+127的距离内)
( 当且仅当(SF XOR OF)=1时,OP1循环控制指令(短转移)
LOOP CX不为零时循环.
LOOPE/LOOPZ CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
JCXZ CX为零时转移.
JECXZ ECX为零时转移.
4>中断指令
INT 中断指令
INTO 溢出中断
IRET 中断返回
5>处理器控制指令
HLT 处理器暂停, 直到出现中断或复位信号才继续.
WAIT 当芯片引线TEST为高电平时使CPU进入等待状态.
ESC 转换到外处理器.
LOCK 封锁总线.
NOP 空操作.
STC 置进位标志位.
CLC 清进位标志位.
CMC 进位标志取反.
STD 置方向标志位.
CLD 清方向标志位.
STI 置中断允许位.
CLI 清中断允许位.

六、伪指令
DW 定义字(2字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.

当然不是所有的指令都能用的上的,我在这里全部写出来是为了让大家认识一下,方便大家以后的学习,我归纳了一下常用的指令,这些指令大家一定要熟练掌握才可以啊。

MOV 数据传送指令
PUSH,POP 堆栈指令
CMP 比较指令
LEA 取地址指令
XOR 异或指令
JE,JZ,JMP…(所有的转移指令)

七:怎么来分析客户端的有关资料?
自己作外挂,大多时候要分析封包,不过因为有的功能是由客户端来辨别的,所以分析客户端的程序同样也很重要,分析客户端首先要求你能看懂汇编指令(只要”看懂”,要求很低的),其次是要能够熟练的运用一些工具,然后能剩下的也就是运气和游戏公司的漏洞了。(哈,不是每次都能成功的啊)下边我分步教给大家。

第一章 8086汇编指令

注:AX,BX,CX…,EAX,EBX,ECX…这些都是CPU用来存储数据的地方。

一、数据传输指令
作用:它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.
1. 通用数据传送指令.
MOV 传送字或字节.
MOVSX 先符号扩展,再传送.
MOVZX 先零扩展,再传送.
PUSH 把字压入堆栈.
POP 把字弹出堆栈.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
BSWAP 交换32位寄存器里字节的顺序
XCHG 交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
XADD 先交换再累加.( 结果在第一个操作数里 )
XLAT 字节查表转换.
BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即 0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL )

2. 输入输出端口传送指令.
IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )
输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,其范围是 0-65535.

3. 目的地址传送指令.
LEA 装入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 传送目标指针,把指针内容装入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 传送目标指针,把指针内容装入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 传送目标指针,把指针内容装入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 传送目标指针,把指针内容装入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 传送目标指针,把指针内容装入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.

4. 标志传送指令.
LAHF 标志寄存器传送,把标志装入AH.
SAHF 标志寄存器传送,把AH内容装入标志寄存器.
PUSHF 标志入栈.
POPF 标志出栈.
PUSHD 32位标志入栈.
POPD 32位标志出栈.

二、算术运算指令
ADD 加法.
ADC 带进位加法.
INC 加 1.
AAA 加法的ASCII码调整.
DAA 加法的十进制调整.
SUB 减法.
SBB 带借位减法.
DEC 减 1.
NEC 求反(以 0 减之).
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果).
AAS 减法的ASCII码调整.
DAS 减法的十进制调整.
MUL 无符号乘法.
IMUL 整数乘法.
以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
AAM 乘法的ASCII码调整.
DIV 无符号除法.
IDIV 整数除法.
以上两条,结果回送:
商回送AL,余数回送AH, (字节运算);
或 商回送AX,余数回送DX, (字运算).
AAD 除法的ASCII码调整.
CBW 字节转换为字. (把AL中字节的符号扩展到AH中去)
CWD 字转换为双字. (把AX中的字的符号扩展到DX中去)
CWDE 字转换为双字. (把AX中的字符号扩展到EAX中去)
CDQ 双字扩展. (把EAX中的字的符号扩展到EDX中去)

三、逻辑运算指令
AND 与运算.
OR 或运算.
XOR 异或运算.
NOT 取反.
TEST 测试.(两操作数作与运算,仅修改标志位,不回送结果).
SHL 逻辑左移.
SAL 算术左移.(=SHL)
SHR 逻辑右移.
SAR 算术右移.(=SHR)
ROL 循环左移.
ROR 循环右移.
RCL 通过进位的循环左移.
RCR 通过进位的循环右移.
以上八种移位指令,其移位次数可达255次.
移位一次时, 可直接用操作码. 如 SHL AX,1.
移位>1次时, 则由寄存器CL给出移位次数.
如 MOV CL,04
SHL AX,CL

四、串指令
DS:SI 源串段寄存器 :源串变址.
ES:DI 目标串段寄存器:目标串变址.
CX 重复次数计数器.
AL/AX 扫描值.
D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
Z标志 用来控制扫描或比较操作的结束.
MOVS 串传送.
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
CMPS 串比较.
( CMPSB 比较字符. CMPSW 比较字. )
SCAS 串扫描.
把AL或AX的内容与目标串作比较,比较结果反映在标志位.
LODS 装入串.
把源串中的元素(字或字节)逐一装入AL或AX中.
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. )
STOS 保存串.
是LODS的逆过程.
REP 当CX/ECX0时重复.
REPE/REPZ 当ZF=1或比较结果相等,且CX/ECX0时重复.
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX0时重复.
REPC 当CF=1且CX/ECX0时重复.
REPNC 当CF=0且CX/ECX0时重复.

五、程序转移指令
1>无条件转移指令 (长转移)
JMP 无条件转移指令
CALL 过程调用
RET/RETF过程返回.
2>条件转移指令 (短转移,-128到+127的距离内)
( 当且仅当(SF XOR OF)=1时,OP1循环控制指令(短转移)
LOOP CX不为零时循环.
LOOPE/LOOPZ CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
JCXZ CX为零时转移.
JECXZ ECX为零时转移.
4>中断指令
INT 中断指令
INTO 溢出中断
IRET 中断返回
5>处理器控制指令
HLT 处理器暂停, 直到出现中断或复位信号才继续.
WAIT 当芯片引线TEST为高电平时使CPU进入等待状态.
ESC 转换到外处理器.
LOCK 封锁总线.
NOP 空操作.
STC 置进位标志位.
CLC 清进位标志位.
CMC 进位标志取反.
STD 置方向标志位.
CLD 清方向标志位.
STI 置中断允许位.
CLI 清中断允许位.

六、伪指令
DW 定义字(2字节).
PROC 定义过程.
ENDP 过程结束.
SEGMENT 定义段.
ASSUME 建立段寄存器寻址.
ENDS 段结束.
END 程序结束.

当然不是所有的指令都能用的上的,我在这里全部写出来是为了让大家认识一下,方便大家以后的学习,我归纳了一下常用的指令,这些指令大家一定要熟练掌握才可以啊。

MOV 数据传送指令
PUSH,POP 堆栈指令
CMP 比较指令
LEA 取地址指令
XOR 异或指令
JE,JZ,JMP…(所有的转移指令)

归类于: 未分类 — nirvana @ 11:18 pm 评论(0)
明天你来自己做外挂

随着网络游戏的日益火爆,很多玩家都投身到游戏中。目前很多玩家都依赖于一些游戏的外挂程序来进行游戏。那么做一个游戏的外挂程序是否是一件很困难的事呢?回答是”否”,诚然编写一些程序是需要一些基本知识的,所以我们想以最简单的语言来给你讲授编写外挂程序的一些技巧,一些方法,并提供给你一些基本的辅助工具,即使你是一个菜鸟,看了我们的教程,并技巧地使用我们提供给你的工具,你完全能够编写出一个完全属于你自己的外挂。在本教程内,我们提供了金庸群侠传,以及网络三国这两个游戏的修改实际例子,因为这两款游戏都是对封包进行过加密运算的,如果你对这两个游戏的修改有了一定的了解后,相信你在其他游戏中也能非常好地做出属于自己的外挂。我们提供了金庸打增援20个NPC和网络三国在PK中自动吃药,自动发镖这两个实际的例子,让你上手更容易。我们会在教程内讲授给你怎么去破解封包的加密算法,怎么利用我们提供给你工具来伪造和发送封包。本教程除了文字教程外,我们还会提供金庸群侠和三国的外挂程序,另外还提供6个外挂制作工具,以供你使用。希望在以后的游戏中,每一个玩家都能够在游戏中成长起来,不但游戏玩的出色,修改游戏也同样出色,做一个真正的游戏DIY。
   
要想在修改游戏中做到百战百胜,是需要相当丰富的计算机知识的。有很多计算机高手就是从玩游戏,修改游戏中,逐步对计算机产生浓厚的兴趣,逐步成长起来的。不要在羡慕别人能够做到的,因为别人能够做的你也能够!我相信你们看了本教程后,会对游戏有一个全新的认识,呵呵,因为我是个好老师!(别拿鸡蛋砸我呀,救命啊!#¥%……*)
    不过要想从修改游戏中学到知识,增加自己的计算机水平,可不能只是靠修改游戏呀!  
要知道,修改游戏只是一个验证你对你所了解的某些计算机知识的理解程度的场所,只能给你一些发现问题、解决问题的机会,只能起到帮助你提高学习计算机的兴趣的作用,而决不是学习计算机的捷径。
一:什么叫外挂?
   
现在的网络游戏多是基于Internet上客户/服务器模式,服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家可以通过运行客户端程序同时登录到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操作。那么既然在网络游戏中有了服务器这个概念,我们以前传统的修改游戏方法就显得无能为力了。记得我们在单机版的游戏中,随心所欲地通过内存搜索来修改角色的各种属性,这在网络游戏中就没有任何用处了。因为我们在网络游戏中所扮演角色的各种属性及各种重要资料都存放在服务器上,在我们自己机器上(客户端)只是显示角色的状态,所以通过修改客户端内存里有关角色的各种属性是不切实际的。那么是否我们就没有办法在网络游戏中达到我们修改的目的?回答是”否”。我们知道Internet客户/服务器模式的通讯一般采用TCP/IP通信协议,数据交换是通过IP数据包的传输来实现的,一般来说我们客户端向服务器发出某些请求,比如移动、战斗等指令都是通过封包的形式和服务器交换数据。那么我们把本地发出消息称为SEND,意思就是发送数据,服务器收到我们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,比如,移动的坐标,战斗的类型。那么我们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来我们要做的工作就是分析客户端和服务器之间往来的数据(也就是封包),这样我们就可以提取到对我们有用的数据进行修改,然后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就可以实现我们修改游戏的目的了。
   
目前除了修改游戏封包来实现修改游戏的目的,我们也可以修改客户端的有关程序来达到我们的要求。我们知道目前各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中所有玩家的状况几乎是不可能的,所以有一些运算还是要依靠我们客户端来完成,这样又给了我们修改游戏提供了一些便利。比如我们可以通过将客户端程序脱壳来发现一些程序的判断分支,通过跟踪调试我们可以把一些对我们不利的判断去掉,以此来满足我们修改游戏的需求。
  在下几个章节中,我们将给大家讲述封包的概念,和修改跟踪客户端的有关知识。大家准备好了吗?

游戏数据格式和存储:  
  在进行我们的工作之前,我们需要掌握一些关于计算机中储存数据方式的知识和游戏中储存数据的特点。本章节是提供给菜鸟级的玩家看的,如果你是高手就可以跳过了,呵呵!
     
如果,你想成为无坚不摧的剑客,那么,这些东西就会花掉你一些时间;如果,你只想作个江湖的游客的话,那么这些东西,了解与否无关紧要。是作剑客,还是作游客,你选择吧!

现在我们开始!首先,你要知道游戏中储存数据的几种格式,这几种格式是:字节(BYTE)、字(WORD)和双字(DOUBLE  
WORD),或者说是8位、16位和32位储存方式。字节也就是8位方式能储存0~255的数字;字或说是16位储存方式能储存0~65535的数;双字即32位方式能储存0~4294967295的数。

为何要了解这些知识呢?在游戏中各种参数的最大值是不同的,有些可能100左右就够了,比如,金庸群侠传中的角色的等级、随机遇敌个数等等。而有些却需要大于255甚至大于65535,象金庸群侠传中角色的金钱值可达到数百万。所以,在游戏中各种不同的数据的类型是不一样的。在我们修改游戏时需要寻找准备修改的数据的封包,在这种时候,正确判断数据的类型是迅速找到正确地址的重要条件。

在计算机中数据以字节为基本的储存单位,每个字节被赋予一个编号,以确定各自的位置。这个编号我们就称为地址。  
    
  在需要用到字或双字时,计算机用连续的两个字节来组成一个字,连续的两个字组成一个双字。而一个字或双字的地址就是它们的低位字节的地址。
  现在我们常用的Windows 9x操作系统中,地址是用一个32位的二进制数表示的。而在平时我们用到内存地址时,总是用一个8位的16进制数来表示它。  
二进制和十六进制又是怎样一回事呢?  
     
简单说来,二进制数就是一种只有0和1两个数码,每满2则进一位的计数进位法。同样,16进制就是每满十六就进一位的计数进位法。16进制有0–F十六个数字,它为表示十到十五的数字采用了A、B、C、D、E、F六个数字,它们和十进制的对应关系是:A对应于10,B对应于11,C对应于12,D对应于13,E对应于14,F对应于15。而且,16进制数和二进制数间有一个简单的对应关系,那就是;四位二进制数相当于一位16进制数。比如,一个四位的二进制数1111就相当于16进制的F,1010就相当于A。

了解这些基础知识对修改游戏有着很大的帮助,下面我就要谈到这个问题。由于在计算机中数据是以二进制的方式储存的,同时16进制数和二进制间的转换关系十分简单,所以大部分的修改工具在显示计算机中的数据时会显示16进制的代码,而且在你修改时也需要输入16进制的数字。你清楚了吧?

在游戏中看到的数据可都是十进制的,在要寻找并修改参数的值时,可以使用Windows提供的计算器来进行十进制和16进制的换算,我们可以在开始菜单里的程序组中的附件中找到它。

现在要了解的知识也差不多了!不过,有个问题在游戏修改中是需要注意的。在计算机中数据的储存方式一般是低位数储存在低位字节,高位数储存在高位字节。比如,十进制数41715转换为16进制的数为A2F3,但在计算机中这个数被存为F3A2。

看了以上内容大家对数据的存贮和数据的对应关系都了解了吗? 好了,接下来我们要告诉大家在游戏中,封包到底是怎么一回事了,来!大家把袖口卷起来,让我们来干活吧!

二:什么是封包?
怎么截获一个游戏的封包?
怎么去检查游戏服务器的ip地址和端口号?
   
Internet用户使用的各种信息服务,其通讯的信息最终均可以归结为以IP包为单位的信息传送,IP包除了包括要传送的数据信息外,还包含有信息要发送到的目的IP地址、信息发送的源IP地址、以及一些相关的控制信息。当一台路由器收到一个IP数据包时,它将根据数据包中的目的IP地址项查找路由表,根据查找的结果将此IP数据包送往对应端口。下一台IP路由器收到此数据包后继续转发,直至发到目的地。路由器之间可以通过路由协议来进行路由信息的交换,从而更新路由表。

那么我们所关心的内容只是IP包中的数据信息,我们可以使用许多监听网络的工具来截获客户端与服务器之间的交换数据,下面就向你介绍其中的一种工具:WPE。  
WPE使用方法:
执行WPE会有下列几项功能可选择:  
SELECT GAME
选择目前在记忆体中您想拦截的程式,您只需双击该程式名称即可。  
TRACE
追踪功能。用来追踪撷取程式送收的封包。
WPE必须先完成点选欲追踪的程式名称,才可以使用此项目。
按下Play键开始撷取程式收送的封包。
您可以随时按下 | | 暂停追踪,想继续时请再按下 | | 。
按下正方形可以停止撷取封包并且显示所有已撷取封包内容。
若您没按下正方形停止键,追踪的动作将依照OPTION里的设定值自动停止。
如果您没有撷取到资料,试试将OPTION里调整为Winsock Version 2。
WPE 及 Trainers 是设定在显示至少16 bits 颜色下才可执行。  
FILTER
过滤功能。用来分析所撷取到的封包,并且予以修改。  
SEND PACKET
送出封包功能。能够让您送出假造的封包。  
TRAINER MAKER
制作修改器。  
OPTIONS
设定功能。让您调整WPE的一些设定值。  
FILTER的详细教学  
    
- 当FILTER在启动状态时 ,ON的按钮会呈现红色。
- 当您启动FILTER时,您随时可以关闭这个视窗。FILTER将会保留在原来的状态,直到您再按一次 on / off 钮。
- 只有FILTER启用钮在OFF的状态下,才可以勾选Filter前的方框来编辑修改。
- 当您想编辑某个Filter,只要双击该Filter的名字即可。  
NORMAL MODE:  
    
范例:  
当您在 Street Fighter Online ﹝快打旋风线上版﹞游戏中,您使用了两次火球而且击中了对方,这时您会撷取到以下的封包:
SEND-> 0000 08 14 21 06 01 04
SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00
SEND-> 0000 03 84 11 09 11 09
SEND-> 0000 0A 09 C1 10 00 00 FF 52 44
SEND-> 0000 0A 09 C1 10 00 00 66 52 44  
您的第一个火球让对方减了16滴﹝16 = 10h﹞的生命值,
而您观察到第4跟第5个封包的位置4有10h的值出现,应该就是这里了。  
您观察10h前的0A 09 C1在两个封包中都没改变,可见得这3个数值是发出火球的关键。  
因此您将0A 09 C1  
10填在搜寻列﹝SEARCH﹞,然后在修改列﹝MODIFY﹞的位置4填上FF。如此一来,当您再度发出火球时,FF会取代之前的10,也就是攻击力为255的火球了!

ADVANCED MODE:  
    
范例:
   
当您在一个游戏中,您不想要用真实姓名,您想用修改过的假名传送给对方。在您使用TRACE后,您会发现有些封包里面有您的名字出现。假设您的名字是Shadow,换算成16进位则是﹝53  
68 61 64 6F 77﹞;而您打算用moon﹝6D 6F 6F 6E 20 20﹞来取代他。
1) SEND-> 0000 08 14 21 06 01 04
2) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05
3) SEND-> 0000 03 84 11 09 11 09
4) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11
5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44  
但是您仔细看,您的名字在每个封包中并不是出现在相同的位置上  
- 在第2个封包里,名字是出现在第4个位置上
- 在第4个封包里,名字是出现在第6个位置上  
在这种情况下,您就需要使用ADVANCED MODE
- 您在搜寻列﹝SEARCH﹞填上:53 68 61 64 6F 77 ﹝请务必从位置1开始填﹞
- 您想要从原来名字Shadow的第一个字母开始置换新名字,因此您要选择从数值被发现的位置开始替代连续数值﹝from the position of the  
chain found﹞。
- 现在,在修改列﹝MODIFY﹞000的位置填上:6D 6F 6F 6E 20 20 ﹝此为相对应位置,也就是从原来搜寻栏的+001位置开始递换﹞
- 如果您想从封包的第一个位置就修改数值,请选择﹝from the beginning of the packet﹞  
   
了解一点TCP/IP协议常识的人都知道,互联网是将信息数据打包之后再传送出去的。每个数据包分为头部信息和数据信息两部分。头部信息包括数据包的发送地址和到达地址等。数据信息包括我们在游戏中相关操作的各项信息。那么在做截获封包的过程之前我们先要知道游戏服务器的IP地址和端口号等各种信息,实际上最简单的是看看我们游戏目录下,是否有一个SERVER.INI的配置文件,这个文件里你可以查看到个游戏服务器的IP地址,比如金庸群侠传就是如此,那么除了这个我们还可以在DOS下使用NETSTAT这个命令,

NETSTAT命令的功能是显示网络连接、路由表和网络接口信息,可以让用户得知目前都有哪些网络连接正在运作。或者你可以使用木马客星等工具来查看网络连接。工具是很多的,看你喜欢用哪一种了。

NETSTAT命令的一般格式为:
NETSTAT [选项]  
命令中各选项的含义如下:
-a 显示所有socket,包括正在监听的。
-c 每隔1秒就重新显示一遍,直到用户中断它。
-i 显示所有网络接口的信息。
-n 以网络IP地址代替名称,显示出网络连接情形。
-r 显示核心路由表,格式同”route -e”。
-t 显示TCP协议的连接情况。
-u 显示UDP协议的连接情况。
-v 显示正在进行的工作。  
三:怎么来分析我们截获的封包?
  首先我们将WPE截获的封包保存为文本文件,然后打开它,这时会看到如下的数据(这里我们以金庸群侠传里PK店小二客户端发送的数据为例来讲解):  
第一个文件:
SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1B
SEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9B
SEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1A
SEND-> 0000 E6 56 1B C0 68 12 12 12 5A
SEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12
SEND-> 0000 E6 56 17 C9 12  
第二个文件:
SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7E
SEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6D
SEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3
SEND-> 0000 83 33 7E A5 21 77 77 77 3F
SEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77
SEND-> 0000 83 33 72 AC 77  
  我们发现两次PK店小二的数据格式一样,但是内容却不相同,我们是PK的同一个NPC,为什么会不同呢?
  原来金庸群侠传的封包是经过了加密运算才在网路上传输的,那么我们面临的问题就是如何将密文解密成明文再分析了。  
  因为一般的数据包加密都是异或运算,所以这里先讲一下什么是异或。
   
简单的说,异或就是”相同为0,不同为1″(这是针对二进制按位来讲的),举个例子,0001和0010异或,我们按位对比,得到异或结果是0011,计算的方法是:0001的第4位为0,0010的第4位为0,它们相同,则异或结果的第4位按照”相同为0,不同为1″的原则得到0,0001的第3位为0,0010的第3位为0,则异或结果的第3位得到0,0001的第2位为0,0010的第2位为1,则异或结果的第2位得到1,0001的第1位为1,0010的第1位为0,则异或结果的第1位得到1,组合起来就是0011。异或运算今后会遇到很多,大家可以先熟悉熟悉,熟练了对分析很有帮助的。

下面我们继续看看上面的两个文件,按照常理,数据包的数据不会全部都有值的,游戏开发时会预留一些字节空间来便于日后的扩充,也就是说数据包里会存在一些”00″的字节,观察上面的文件,我们会发现文件一里很多”12″,文件二里很多”77″,那么这是不是代表我们说的”00″呢?推理到这里,我们就开始行动吧!

我们把文件一与”12″异或,文件二与”77″异或,当然用手算很费事,我们使用”M2M 1.0 加密封包分析工具”来计算就方便多了。得到下面的结果:  
第一个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89
2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 08
3 SEND-> 0000 F4 44 09 D2 7A 00 00 00 48
4 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00
5 SEND-> 0000 F4 44 05 DB 00  
第二个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A
2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 84
3 SEND-> 0000 F4 44 09 D2 56 00 00 00 48
4 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 00
5 SEND-> 0000 F4 44 05 DB 00  
  哈,这一下两个文件大部分都一样啦,说明我们的推理是正确的,上面就是我们需要的明文!  
  接下来就是搞清楚一些关键的字节所代表的含义,这就需要截获大量的数据来分析。  
  首先我们会发现每个数据包都是”F4 44″开头,第3个字节是变化的,但是变化很有规律。我们来看看各个包的长度,发现什么没有?对了,第3个字节就是包的长度!
  通过截获大量的数据包,我们判断第4个字节代表指令,也就是说客户端告诉服务器进行的是什么操作。例如向服务器请求战斗指令为”30″,战斗中移动指令为”D4″等。
  接下来,我们就需要分析一下上面第一个包”F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF  
26 00 00 00 00 05 00 1C 00 00 00  
89″,在这个包里包含什么信息呢?应该有通知服务器你PK的哪个NPC吧,我们就先来找找这个店小二的代码在什么地方。
  我们再PK一个小喽罗(就是大理客栈外的那个咯):
SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09
SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0
   
我们根据常理分析,游戏里的NPC种类虽然不会超过65535(FFFF),但开发时不会把自己限制在字的范围,那样不利于游戏的扩充,所以我们在双字里看看。通过”店小二”和”小喽罗”两个包的对比,我们把目标放在”6C  
79 F6 05″和”CF 26 00  
00″上。(对比一下很容易的,但你不能太迟钝咯,呵呵)我们再看看后面的包,在后面的包里应该还会出现NPC的代码,比如移动的包,游戏允许观战,服务器必然需要知道NPC的移动坐标,再广播给观战的其他玩家。在后面第4个包”SEND->  
0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00″里我们又看到了”6C 79 F6  
05″,初步断定店小二的代码就是它了!
(这分析里边包含了很多工作的,大家可以用WPE截下数据来自己分析分析)  
第一个包的分析暂时就到这里(里面还有的信息我们暂时不需要完全清楚了)  
我们看看第4个包”SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00  
00″,再截获PK黄狗的包,(狗会出来2只哦)看看包的格式:
SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00
SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00  
根据上面的分析,黄狗的代码为”4B 7D F6 05″(100040011),不过两只黄狗服务器怎样分辨呢?看看”EB 03 F8  
05″(100140011),是上一个代码加上100000,呵呵,这样服务器就可以认出两只黄狗了。我们再通过野外遇敌截获的数据包来证实,果然如此。  
   
那么,这个包的格式应该比较清楚了:第3个字节为包的长度,”DA”为指令,第5个字节为NPC个数,从第7个字节开始的10个字节代表一个NPC的信息,多一个NPC就多10个字节来表示。

大家如果玩过网金,必然知道随机遇敌有时会出现增援,我们就利用游戏这个增援来让每次战斗都会出现增援的NPC吧。  
  通过在战斗中出现增援截获的数据包,我们会发现服务器端发送了这样一个包:
F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00
第5-第8个字节为增援NPC的代码(这里我们就简单的以黄狗的代码来举例)。
  那么,我们就利用单机代理技术来同时欺骗客户端和服务器吧!  
  好了,呼叫NPC的工作到这里算是完成了一小半,接下来的事情,怎样修改封包和发送封包,我们下节继续讲解吧。  
四:怎么冒充”客户端”向”服务器”发我们需要的封包?
  这里我们需要使用一个工具,它位于客户端和服务器端之间,它的工作就是进行数据包的接收和转发,这个工具我们称为代理。
如果代理的工作单纯就是接收和转发的话,这就毫无意义了,但是请注意:所有的数据包都要通过它来传输,这里的意义就重大了。我们可以分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造我们需要的封包来发送。

下面我们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。
  通过我们上节的分析,我们已经知道了打多个NPC的封包格式,那么我们就动手吧!  
  首先我们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,我们找”F4 44 1F 30″这个特征,这是不会改变的,当然是要解密后来查找哦。
  找到后,表示客户端在向服务器请求战斗,我们不动这个包,转发。
   
继续向下查找,这时需要查找的特征码不太好办,我们先查找”DA”,这是客户端发送NPC信息的数据包的指令,那么可能其他包也有”DA”,没关系,我们看前3个字节有没有”F4  
44″就行了。找到后,我们的工作就开始了!  
   
我们确定要打的NPC数量。这个数量不能很大,原因在于网金的封包长度用一个字节表示,那么一个包可以有255个字节,我们上面分析过,增加一个NPC要增加10个字节,所以大家算算就知道,打20个NPC比较合适。

然后我们要把客户端原来的NPC代码分析计算出来,因为增加的NPC代码要加上100000哦。再把我们增加的NPC代码计算出来,并且组合成新的封包,注意代表包长度的字节要修改啊,然后转发到服务器,这一步在编写程序的时候要注意算法,不要造成较大延迟。

上面我们欺骗服务器端完成了,欺骗客户端就简单了,^-^  
  发送了上面的封包后,我们根据新增NPC代码构造封包马上发给客户端,格式就是”F4 44 12 E9 NPC代码 02 00 00 03 00 00 00  
00 00 00″,把每个新增的NPC都构造这样一个包,按顺序连在一起发送给客户端,客户端也就被我们骗过了,很简单吧。  
  以后战斗中其他的事我们就不管了,尽情地开打吧,呵呵。  
  上面讲的需要一定的编程基础,但是不难,即使你不会编程,相信你继续看下去就会有收获了。

四:怎么冒充”客户端”向”服务器”发我们需要的封包?
  这里我们需要使用一个工具,它位于客户端和服务器端之间,它的工作就是进行数据包的接收和转发,这个工具我们称为代理。
如果代理的工作单纯就是接收和转发的话,这就毫无意义了,但是请注意:所有的数据包都要通过它来传输,这里的意义就重大了。我们可以分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造我们需要的封包来发送。

下面我们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。
  通过我们上节的分析,我们已经知道了打多个NPC的封包格式,那么我们就动手吧!  
  首先我们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,我们找”F4 44 1F 30″这个特征,这是不会改变的,当然是要解密后来查找哦。
  找到后,表示客户端在向服务器请求战斗,我们不动这个包,转发。
   
继续向下查找,这时需要查找的特征码不太好办,我们先查找”DA”,这是客户端发送NPC信息的数据包的指令,那么可能其他包也有”DA”,没关系,我们看前3个字节有没有”F4  
44″就行了。找到后,我们的工作就开始了!  
   
我们确定要打的NPC数量。这个数量不能很大,原因在于网金的封包长度用一个字节表示,那么一个包可以有255个字节,我们上面分析过,增加一个NPC要增加10个字节,所以大家算算就知道,打20个NPC比较合适。

然后我们要把客户端原来的NPC代码分析计算出来,因为增加的NPC代码要加上100000哦。再把我们增加的NPC代码计算出来,并且组合成新的封包,注意代表包长度的字节要修改啊,然后转发到服务器,这一步在编写程序的时候要注意算法,不要造成较大延迟。

上面我们欺骗服务器端完成了,欺骗客户端就简单了,^-^  
  发送了上面的封包后,我们根据新增NPC代码构造封包马上发给客户端,格式就是”F4 44 12 E9 NPC代码 02 00 00 03 00 00 00  
00 00 00″,把每个新增的NPC都构造这样一个包,按顺序连在一起发送给客户端,客户端也就被我们骗过了,很简单吧。  
  以后战斗中其他的事我们就不管了,尽情地开打吧,呵呵。  
  上面讲的需要一定的编程基础,但是不难,即使你不会编程,相信你继续看下去就会有收获了。

五:怎么用计算机语言去写一个单机代理?
   
在上一章,我们已经对于代理的原理进行了讲解,大家对于代理已经有了一个初步的认识,现在我教大家如何用计算机语言编写一个自己的代理,我们考虑到简单明了,我们选用VB,因为用VB编写代理只需要很少的代码。

代码如下:  
Private Sub form_Load()
  DaiLi.LocalPort = “1234″
  Server.RemotePort = “1234″
  Server.RemoteHost = “211.100.20.26″
  DaiLi.Listen
End Sub  
Private Sub DaiLi_ConnectionRequest(ByVal requestID As Long)
  Server.Connect
  Client.Accept requestID
End Sub  
Private Sub Client_DataArrival(ByVal bytesTotal As Long)
  Dim ClientToServer() As Byte
  Client.GetData ClientToServer
  Server.SendData ClientToServer
End Sub  
Private Sub Server_DataArrival(ByVal bytesTotal As Long)
  Dim ServerToClient() As Byte
  Server.GetData ServerToClient
  Client.SendData ServerToClient
End Sub  
form_Load()这个过程表示在程序启动的时候要做的一些初始化操作。
DaiLi.LocalPort = “1234″ 设定监听端口
Server.RemotePort = “1234″ 设定象游戏服务器连接的端口(和监听端口是相同的)
Server.RemoteHost = “211.100.20.26″ 设定游戏服务器的IP地址
DaiLi.Listen 监听本地的连接请求
这时你只要将游戏的服务器列表的IP改成127.0.0.1,那么游戏的客户端程序就会来连接我们的代理,我们的代理会调用如下的过程:
Private Sub DaiLi_ConnectionRequest(ByVal requestID As Long)
  Server.Connect 代理客户端向服务器连接
  Client.Accept requestID 接受客户端的连接请求
End Sub  
当客户端向服务器发送数据时,就会调用下边的过程
Private Sub Client_DataArrival(ByVal bytesTotal As Long)
  Dim ClientToServer() As Byte 变量定义,请求了一个用于存放数据的空间
  Client.GetData ClientToServer 客户端的连接接收这些数据
  在这里我们可以添加自己的代码,对封包进行修改,然后再发向服务器。  
  Server.SendData ClientToServer 服务器的连接把这些数据发向服务器
End Sub  
当服务器发送数据给客户端时,会调用下边的过程
Private Sub Server_DataArrival(ByVal bytesTotal As Long)
  Dim ServerToClient() As Byte 变量定义,请求了一个用于存放数据的空间
  Server.GetData ServerToClient 服务器连接接收数据
  在这里我们可以添加自己的代码,对封包进行修改,然后再发给客户端。  
  Client.SendData ServerToClient
End Sub  
   
用其他语言编写基本的原理也是差不多的,不过可能稍微要麻烦一些,因为VB本身有一个MSWINSCK.OCX控件,这个控件封装了WINDOWS的网络操作,而且接口很简单,推荐大家使用。

六:如果单机代理被封,我们怎么利用底层的技术来接管游戏的发包?
     
在WINDOWS系统中,网络通讯的任务是由一个叫WSOCK32.DLL(在SYSTEM目录下)来完成的,每当游戏被运行时,他都会自动的去调用这个动态连接库,因为在WINDOWS系统中对于文件的搜索顺序是  
程序目录>系统目录>路径中设置的目录,所以我们就有机会替换掉系统的WSOCK32.DLL使的游戏调用我们的WSOCK32.DLL,这样我们就有了对于游戏封包绝对的控制权,有人问:”我们应该怎么做呢?”,我们只要自己编写一个WSOCK32.DLL放到游戏的目录下,就OK了,当然让我们完全自己去编写一个WSOCK32.DLL是不太现实的,因为本身网络通讯要处理很多更底层的东西,比如说从网卡读取BIT流,所以我们选择由我们的WSOCK32.DLL去调用系统的WSOCK32.DLL来完成这个功能。

WSOCK32.DLL有很多的输出函数,函数如下:
__WSAFDIsSet
accept
AcceptEx
Arecv
Asend
bind
closesocket
closesockinfo
connect
dn_expand
EnumProtocolsA
EnumProtocolsW
GetAcceptExSockaddrs
GetAddressByNameA
GetAddressByNameW
gethostbyaddr
gethostbyname
gethostname
GetNameByTypeA
GetNameByTypeW
getnetbyname
getpeername
getprotobyname
getprotobynumber
getservbyname
getservbyport
GetServiceA
GetServiceW
getsockname
getsockopt
GetTypeByNameA
GetTypeByNameW
htonl
htons
inet_addr
inet_network
inet_ntoa
ioctlsocket
listen
MigrateWinsockConfiguration
NPLoadNameSpaces
NSPStartup
ntohl
ntohs
rcmd
recv
recvfrom
rexec
rresvport
s_perror
select
send
sendto
sethostname
SetServiceA
SetServiceW
setsockopt
shutdown
socket
TransmitFile
WEP
WSAAsyncGetHostByAddr
WSAAsyncGetHostByName
WSAAsyncGetProtoByName
WSAAsyncGetProtoByNumber
WSAAsyncGetServByName
WSAAsyncGetServByPort
WSAAsyncSelect
WSACancelAsyncRequest
WSACancelBlockingCall
WSACleanup
WSAGetLastError
WSAIsBlocking
WSApSetPostRoutine
WSARecvEx
WSASetBlockingHook
WSASetLastError
WSAStartup
WSAUnhookBlockingHook
WsControl
WSHEnumProtocols  
  在这里,不是所有的函数都要修改,因为我们只关心发送和接收的封包,所以我们只要修改send  
和recv两个函数,前者是发送封包的后者是接收封包的,我们在这两个函数的处理中加入我们自己的代码,来完成封包的辨认,修改以及转发等功能。  
六:如果单机代理被封,我们怎么利用底层的技术来接管游戏的发包?
     
在WINDOWS系统中,网络通讯的任务是由一个叫WSOCK32.DLL(在SYSTEM目录下)来完成的,每当游戏被运行时,他都会自动的去调用这个动态连接库,因为在WINDOWS系统中对于文件的搜索顺序是  
程序目录>系统目录>路径中设置的目录,所以我们就有机会替换掉系统的WSOCK32.DLL使的游戏调用我们的WSOCK32.DLL,这样我们就有了对于游戏封包绝对的控制权,有人问:”我们应该怎么做呢?”,我们只要自己编写一个WSOCK32.DLL放到游戏的目录下,就OK了,当然让我们完全自己去编写一个WSOCK32.DLL是不太现实的,因为本身网络通讯要处理很多更底层的东西,比如说从网卡读取BIT流,所以我们选择由我们的WSOCK32.DLL去调用系统的WSOCK32.DLL来完成这个功能。

WSOCK32.DLL有很多的输出函数,函数如下:
__WSAFDIsSet
accept
AcceptEx
Arecv
Asend
bind
closesocket
closesockinfo
connect
dn_expand
EnumProtocolsA
EnumProtocolsW
GetAcceptExSockaddrs
GetAddressByNameA
GetAddressByNameW
gethostbyaddr
gethostbyname
gethostname
GetNameByTypeA
GetNameByTypeW
getnetbyname
getpeername
getprotobyname
getprotobynumber
getservbyname
getservbyport
GetServiceA
GetServiceW
getsockname
getsockopt
GetTypeByNameA
GetTypeByNameW
htonl
htons
inet_addr
inet_network
inet_ntoa
ioctlsocket
listen
MigrateWinsockConfiguration
NPLoadNameSpaces
NSPStartup
ntohl
ntohs
rcmd
recv
recvfrom
rexec
rresvport
s_perror
select
send
sendto
sethostname
SetServiceA
SetServiceW
setsockopt
shutdown
socket
TransmitFile
WEP
WSAAsyncGetHostByAddr
WSAAsyncGetHostByName
WSAAsyncGetProtoByName
WSAAsyncGetProtoByNumber
WSAAsyncGetServByName
WSAAsyncGetServByPort
WSAAsyncSelect
WSACancelAsyncRequest
WSACancelBlockingCall
WSACleanup
WSAGetLastError
WSAIsBlocking
WSApSetPostRoutine
WSARecvEx
WSASetBlockingHook
WSASetLastError
WSAStartup
WSAUnhookBlockingHook
WsControl
WSHEnumProtocols  
  在这里,不是所有的函数都要修改,因为我们只关心发送和接收的封包,所以我们只要修改send  
和recv两个函数,前者是发送封包的后者是接收封包的,我们在这两个函数的处理中加入我们自己的代码,来完成封包的辨认,修改以及转发等功能。  
第二章 一些工具的使用
用到的工具包括Fi2.9,Wasm8.9 ,UltraEdit  
一、 Fi2.9
   
因为现在软件为了保护,另外也为了减少程序的大小,都采用了加壳技术。有人问了”壳是什么呢”,在自然界中,我想大家对”壳”这东西应该都不会陌生了,植物用它来保护种子,动物用它来保护身体等等。同样,在一些计算机软件里也有一段专门负责保护软件不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。就像动植物的壳一般都是在身体外面一样理所当然。由于这段程序和自然界的壳在功能上有很多相同的地方,基于命名的规则,大家就把这样的程序称为”壳”了。
   
现在我们已经知道”壳”是用来保护程序的,那我们要分析游戏程序,必须先把他的这层壳退掉,我们称之为”脱壳”,这个很容易的,因为现在网上有很多的脱壳软件可以脱壳,不过在脱壳前我们必须先知道程序用什么加的”壳”,Fi就是这样一个软件,操作很简单,将要分析的软件和Fi放在同一个目录,然后直接运行Fi,在按回车就OK了

二、Wasm8.9
  这是一个用来反汇编程序的软件(反汇编,就是把机器代码转变为汇编代码)。操作很简单,

首先打开文件,进行反汇编操作,然后选择”串式参考”找到在程序中出现的内容,定位到该地址,进行分析,分析完成,进行修改,加亮当前要修改的指令,然后看”当前指令在文件中的地址偏移”,记下这个值。

三UltraEdit
  是一个功能强大的编辑软件,能直接修改程序的16进制指令代码。

实例解析
  下边以金庸的随地逃为例进行解析。
  首先将金庸的Loginp.exe文件和Fi放在同一个目录,然后运行Fi。  
    
   
可以看到程序用UPX加过壳了,所以首先进行脱壳(由于该文件用UPX加壳后进行了修改,用现成的工具无法脱壳,鉴于会员当前水平的考虑,这个文件的脱壳方法以后讲给大家听,我们直接提供一个脱过壳的文件给会员用于会员的学习),脱过壳之后,运行Wasm进行反汇编,然后点”串式参考”,找到”次处非逃离点”

程序如下(请打开MagicWin)  
:00616376 6681B8EC470000AD65 cmp word ptr [eax+000047EC], 65AD”発瞒ア毖”
|
:006163D5 BA5C666100 mov edx, 0061665C
:006163DA E83D9BF7FF call 0058FF1C
:006163DF 8A8368010000 mov al, byte ptr [ebx+00000168]
:006163E5 04F9 add al, F9
:006163E7 2C02 sub al, 02
:006163E9 7320 jnb 0061640B
:006163EB 33C9 xor ecx, ecx
:006163ED 33D2 xor edx, edx
:006163EF B0DE mov al, DE
:006163F1 E8C6AE0100 call 006312BC
:006163F6 EB13 jmp 0061640B  
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0061637F(C)
|
:006163F8 A11CA36700 mov eax, dword ptr [0067A31C]
:006163FD 8B00 mov eax, dword ptr [eax]
:006163FF 33C9 xor ecx, ecx  
* Possible StringData Ref from Data Obj ->”矪獶発瞒翴”
|
:00616401 BA70666100 mov edx, 00616670
:00616406 E8119BF7FF call 0058FF1C  
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:006163CA(U), :006163E9(C), :006163F6(U)
|
:0061640B B201 mov dl, 01
:0061640D 8BC3 mov eax, ebx
:0061640F E8BCC10000 call 006225D0
:00616414 C6836801000000 mov byte ptr [ebx+00000168], 00
:0061641B E977010000 jmp 00616597
:00616420 888368010000 mov byte ptr [ebx+00000168], al
:00616426 C68300D1650000 mov byte ptr [ebx+0065D100], 00
:0061642D 33C9 xor ecx, ecx
:0061642F 33D2 xor edx, edx
:00616431 B0DE mov al, DE
:00616433 E884AE0100 call 006312BC
:00616438 B201 mov dl, 01  
所以我们只要让上边两个注释的地方都不要跳走,就可以实现随时随地逃的功能了,修改很简单,我们把上边的两个跳转语句全部注销掉就OK了,汇编语言里注销语句为NOP  
转为机器指令也就是90,所以修改如下  
原文
:00616376 6681B8EC470000AD65 cmp word ptr [eax+000047EC], 65ADGoto Line/Page
来到地址0×21637f 可以看到这里的两个字节内容为7577(jne 006163f8)把两个字节内容改为9090,
来到地址0×21638a 可以看到这里的两个字节内容为7440(je  
006163cc)把两个字节内容改为9090,然后保存进入金庸游戏看看,是不是可以随地逃了啊,:)  
注:教程中提供的地址会随着loginp.exe的版本的不同有所改变,教程中采用的文件为1月7号的版本。  
大家可以仿照金庸外挂的制作方法和原理制作别的外挂,只要你细心仔细的读这篇文章,你一定会有所成就的!

归类于: , 未分类 — nirvana @ 11:17 pm 评论(0)
教你如何编写游戏外挂

一、先说一下写一个外挂需要什么条件

1、熟练的C语言知识

目前的外挂大部分都是用BC或者是Delphi或者是vc写的,拥有熟练的C语言知识是写外挂的基本条件!

2、具有很强的汇编基础

一般游戏都不可能有原代码的,必须靠反汇编或者跟踪的办法来探索其中的机理

,所以有强的汇编基础也是必不可少的条件

3、熟练掌握跟踪和调试的工具

有了上面2个条件后,掌握一些工具也是很有必要的

跟踪的工具,softice当然是不二之选,至于反汇编的工具,我推荐用IDA PRO

这个工具反汇编出来的代码结构清晰,非常好读

如果你不具有上面的条件,还是先把基础打好,再来写外挂吧,一分耕耘,一分收获,天下没有白掉的馅饼的

二、写外挂面临的基本技术问题

1、修改进程的执行代码

要修改进程的执行代码,要先取得进程的ID,如果是由外挂程序启动,返回值里就有进程ID,如果不是的话,

需要用findwindow找到窗口句柄,再用GetWindowProcessID取得进程ID,取得进程ID以后,就可以用

writeprocessmemory来修改进程的执行代码了,使程序按照我们的意愿来执行,石器外挂里的不遇敌、寸步遇敌

就是用这样的方法来实现的

2、截获外挂发送和接收的封包

除了通过修改代码来实现的功能以外,很多的功能都是通过修改封包来实现的,要修改封包,首先要能截获它。

第一步是要跟踪出发和收的位置,至于怎么跟踪,我以后会提到,找到位置以后,有2个办法,一是在那个位置加一

个jmp语句,跳到你的处理函数位置,处理完后,再跳回来,这种方法要求比较高,需要处理好很多事情,另一种办法

是往那个位置写条能造成例外的指令,比如int 3,然后用DebugActiveProcess调试游戏进程,这样每当游戏执行到那个

位置的时候,就会停下来,到外挂程序里面去,等外挂程序处理完以后,用ContinueDebugEvent 继续运行程序。

今天先写这么多,下回将讨论外挂的具体功能该怎么实现

今天来谈谈地址的调查问题,地址调查是写外挂中最艰辛,最富有挑战性的事情,很多朋友问我要外挂的原程序,其实有了外挂原程序,如果你不会调查地址,还是没用的,

原程序和地址的关系就象武学中招式与内功的关系,没有内功的招式,只是一个花架子。而内功精深以后,任何普通的招式,都有可能化腐朽为神奇,外挂中的地址分为两类,一类是程序地址,一类是数据地址。象石器中的双石器,真彩,不遇敌,寸步遇敌,发送接收封包等,都属于第一类,而人物坐标,状态等,都属于第二类。对于第一类地址,主要依靠softice来调查地址,对第二类地址,可以用一些游戏工具,比如fpe,game expert,game master等来调查,我一直用game expert,因为我找不到2000下能用的fpe,

各位以前用fpe改游戏的时候,没想过他也能用来干这个吧

对于第二类数据的调查方法,大部分人都很熟习了,我就不多说了,现在主要来谈谈第一类数据的详细调查过程,比如我们要调查发送封包的位置,如何着手呢,客户端往服务器要发很多封包,但最简单的办法莫过从说话的封包入手,先说一句很长的话,最好是英文,查起来方便,说完以后,用任意一种办法进入游戏程序的进程空间(比如先用spy查出游戏程序的窗口句柄,再切换到softice打入bmsg 窗口句柄 wm_lbuttondown,这样在游戏程序中一点鼠标就进入了他的进程空间)然后用s命令查出这句话所放的内存地址,记下这个地址,在softice中打入bpm 刚才调查到的地址,这个指令的意思是只要有访问这个内存的动作,立刻中断,然后再切换到游戏,说一句话,你会发现softice自动中断到某一个位置了,从这个位置跟踪下去,发送封包的位置也就不远了。

上面所说的都是针对一个全新的游戏程序而言,如果是一个老的程序,有前辈做了大量的工作,还可以用些别的办法,如反汇编等,来调查。以后游戏版本的更新也是如此,只要把老版本的地址位置附近的代码记下来,去新版本的代码里面search一下,就ok了。

恩,休息一会儿,休息一会儿

我主要对外挂的技术进行分析,至于游戏里面的内部结构每个都不一样,这里就不做讲解了,我也没有那么厉害,所有的都知道,呵呵!
1 首先游戏外挂的原理
外挂现在分为好多种,比如模拟键盘的,鼠标的,修改数据包的,还有修改本地内存的,但好像没有修改服务器内存的哦,呵呵!其实修改服务器也是有办法的,只是技术太高一般人没有办法入手而已!(比如请GM去夜总会,送礼,收黑钱等等办法都可以修改服务器数据,哈哈)
修改游戏无非是修改一下本地内存的数据,或者截获api函数等等,这里我把所能想到的方法都作一个介绍,希望大家能做出很好的外挂来使游戏厂商更好的完善自己的技术.
我见到一片文章是讲魔力宝贝的理论分析,写的不错,大概是那个样子.
下来我就讲解一下技术方面的东西,以作引玉之用
2 技术分析部分
1 模拟键盘或鼠标的响应
我们一般使用UINT SendInput(
UINT nInputs, // count of input events
LPINPUT pInputs, // array of input events
int cbSize // size of structure
);api函数
第一个参数是说明第二个参数的矩阵的维数的,第二个参数包含了响应事件,这个自己填充就可以,最后是这个结构的大小,非常简单,这是最简单的方法模拟键盘鼠标了,呵呵
注意:这个函数还有个替代函数:
VOID keybd_event(
BYTE bVk, // 虚拟键码
BYTE bScan, // 扫描码
DWORD dwFlags,
ULONG_PTR dwExtraInfo // 附加键状态
);和
VOID mouse_event(
DWORD dwFlags, // motion and click options
DWORD dx, // horizontal position or change
DWORD dy, // vertical position or change
DWORD dwData, // wheel movement
ULONG_PTR dwExtraInfo // application-defined information
);
这两个函数非常简单了,我想那些按键精灵就是用的这个吧,呵呵,上面的是模拟键盘,下面的是模拟鼠标的.
这个仅仅是模拟部分,要和游戏联系起来我们还需要找到游戏的窗口才行,或者包含快捷键,就象按键精灵的那个激活键一样,我们可以用GetWindow函数来枚举窗口,也可以用Findwindow函数来查找制定的窗口(注意还有一个FindWindowEx),FindwindowEx可以找到窗口的子窗口,比如按钮,等什么东西.当游戏切换场景的时候我们可以用FindWindowEx来确定一些当前窗口的特征,从而判断是否还在这个场景,方法很多了,比如可以GetWindowInfo来确定一些东西,比如当查找不到某个按钮的时候就说明游戏场景已经切换了,等等办法.有的游戏没有控件在里面,这是对图像做坐标变换的话,这种方法就要受到限制了.这就需要我们用别的办法来辅助分析了.
至于快捷键我们要用动态连接库实现了,里面要用到hook技术了,这个也非常简单,大家可能都会了,其实就是一个全局的hook对象然后SetWindowHook就可以了,回调函数都是现成的,而且现在网上的例子多如牛毛,这个实现在外挂中已经很普遍了.如果还有谁不明白,那就去看看msdn查找SetWindowHook就可以了.

这个动态连接库的作用很大,不要低估了哦,它可以切入所有的进程空间,也就是可以加载到所有的游戏里面哦,只要用对,你会发现很有用途的!
这个需要你复习一下win32编程的基础知识了,呵呵,赶快去看书吧!

2截获消息
有些游戏的响应机制比较简单,是基于消息的,或者用什么定时器的东西,这个时候你就可以用拦截消息来实现一些有趣的功能了.
我们拦截消息使用的也是hook技术,里面包括了键盘消息,鼠标消息,系统消息,日志等,别的对我们没有什么大的用处,我们只用拦截消息的回调函数就可以了,这个不会让我写例子吧,其实这个和上面的一样,都是用SetWindowHook来写的,看看就明白了很简单的.
至于拦截了以后做什么就是你的事情了,比如在每个定时器消息里面处理一些我们的数据判断,或者在定时器里面在模拟一次定时器,那么有些数据就会处理两次,呵呵,后果嘛,不一定是好事情哦,呵呵,不过如果数据计算放在客户端的游戏就可以真的改变数据了,呵呵,试试看吧!用途还有很多,自己想也可以想出来的,呵呵!

3拦截socket包
这个技术难度要比原来的高很多哦,要有思想准备.
首先我们要替换winSock.dll或者winsock32.dll,我们写的替换函数要和原来的函数一致才行,就是说它的函数输出什么样的,我们也要输出什么样子的函数,而且参数,参数顺序都要一样才行,然后在我们的函数里面调用真正的winSock32.dll里面的函数就可以了
首先:我们可以替换动态库到系统路径
其次:我们应用程序启动的时候可以加载原有的动态库,用这个函数LoadLibary
然后定位函数入口用GetProcAddress函数获得每个真正socket函数的入口地址
当游戏进行的时候它会调用我们的动态库,然后从我们的动态库中处理完毕后才跳转到真正动态库的函数地址,这样我们就可以在里面处理自己的数据了,应该是一切数据.呵呵!
兴奋吧,拦截了数据包我们还要分析之后才能进行正确的应答,不要以为这样工作就完成了,呵呵!还早呢,等分析完毕以后我们还要仿真应答机制来和服务器通信,一个不小心就会被封号,呵呵,呜~~~~~~~~我就被封了好多啊!
分析数据才是工作量的来源呢,游戏每次升级有可能加密方式会有所改变,因此我们写外挂的人都是亡命之徒啊,被人娱乐了还不知道,呵呵!(声明我可没有赚钱,我是免费的)
好了,给大家一个不错的起点,这里有完整的替换socket源代码,呵呵!
http://www.vchelp.net/vchelp/zsrc/wsock32_sub.zip

4截获api
上面的技术如果可以灵活运用的话我们就不用截获api函数了,其实这种技术是一种补充技术.比如我们需要截获socket以外的函数作为我们的用途,我们就要用这个技术了,其实我们也可以用它直接拦截在socket中的函数,这样更直接.
现在拦截api的教程到处都是,我就不列举了,我用的比较习惯的方法是根据输入节进行拦截的,这个方法可以用到任何一种操作系统上,比如98/2000等,有些方法不是跨平台的,我不建议使用.这个技术大家可以参考windows核心编程里面的545页开始的内容来学习,如果是98系统可以用window系统奥秘那个最后一章来学习.
好了方法就是这么多了,看大家怎么运用了,其它的一些针对性的技巧这里我就不说了,要不然会有人杀了我的,呵呵!

记住每个游戏的修改方法都不一样,如果某个游戏数据处理全部在服务器端,那么你还是别写外挂了,呵呵,最多写个自动走路的外挂,哈哈!
数据分析的时候大家一定要注意,不要轻易尝试和服务器的连接,因为那有很危险,切忌!等你掌握了大量的数据分析结果以后,比较有把握了在试试,看看你的运气好不好,很有可能会成功的哦,呵呵!
其实像网金也疯狂的那种模拟客户端的程序也是不错的,很适合office的人用,就看大家产品定位了.
好了不说了,大家努力吧!切忌不要被游戏厂商招安哦,那样有损我们的形象,我们是为了让游戏做的更好而开发的,也不愿意打乱游戏的平衡,哎,好像现在不是这样了!不说了随其自然吧!

如何面对高水平的破解组织 谈暴力破解应对相关推荐

  1. 如何防止自己的云服务器被暴力破解密码(限制暴力破解并发送邮件到自己的邮箱)

    如何防止自己的云服务器被暴力破解密码(限制暴力破解并发送邮件到自己的邮箱) 今天看到自己的阿里云服务器被别有用心的人gank一波,于是才想到得设置一套完美的防火墙策略了 首先黑客登录失败的日志为/va ...

  2. 口令暴力破解--Telnet协议暴力破解、数据库暴力破解与远程桌面暴力破解

    Telnet协议暴力破解 Telnet Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式.它为用户提供了在本地计算机上完成远程主机工作的能力.要开始一个 ...

  3. 010破解(一)之暴力破解

    010破解(一)之暴力破解 010介绍 010是非常好用的16进制编辑工具 功能介绍: 16进制修改 文本修改 模板解析各种文件格式 对比文件 暴力破解分析 分析思路: 找到注册窗口 测试注册窗口反应 ...

  4. 【PC工具】压缩包密码破解工具,暴力破解压缩包密码,解压密码获取

    公众号关注 "DLGG创客DIY" 设为"星标",重磅干货,第一时间送达. 平时备份文件,为保证数据安全,经常会给压缩包加密,那么问题就来了,我脑子不好老忘事怎 ...

  5. 【PC工具】压缩包密码破解工具,暴力破解压缩包密码,zip密码获取

    平时备份文件,为保证安全经常会给压缩包加密,但是自己使用的时候又比较容易忘掉.还有一种情况,在网上找了半天,下载了半天的资源,打开压缩包发现有密码... 今天给大家带来一款可以破解压缩包密码的软件-- ...

  6. 破解wifi密码 暴力破解 保姆式教学

    首先需要安装kali系统 这个系统是安装在虚拟机上的 还需要一个无线网卡(最好是kali系统免驱的 否则是无法识别的) 有着两个工具就可以破解密码了 kali官网:Kali Linux | Penet ...

  7. 无形胜有形——0day破解组织探秘 http://blog.csdn.net/jiangtao/archive/2002/11/30/1868.aspx

    又到了TT谈破解的时间了.TT记得以前有人写了一篇<WAREZ无形帝国>的文章,非常不错.不过该文对Warez以及0day的评述难免有失偏颇,TT现在就谈谈自己的看法. 首先谈谈什么是 ...

  8. 安全测试(三) 服务器安全 渗透测试 常规服务器安全漏洞 高危端口暴露、弱口令密码、暴力破解、服务器提权、Mysql数据库提权等 浅谈《社会工程学》实战案例

    文章目录 一.前言 二.服务器安全 高危端口 2.1 端口号(Port number) 2.2 常见端口后及其用途表 2.3 端口号 扫描工具介绍 2.4 端口号 端口说明 攻击技巧 2.5 安全警示 ...

  9. 浅谈软件破解的原理和经验

    我想今天就向大家介绍一下关于暴力破解的知识和破解经验.这个当然也是我个人的经验,但是对于高手来说应还有其它方面的经验,如果你认为我的经验不足的话,希望能够来信补充一下. 现在就开始介绍暴力破解的原理. ...

最新文章

  1. 「小程序JAVA实战」小程序的举报功能开发(68)
  2. Vijos1683 有根树的同构问题
  3. CVPR 2020 | 基于多智能体RL实现多轮连续交互,IteR-MRL使图像分割算法达到医用标准...
  4. 【HTML+CSS练习】画一个条件查询
  5. 编程大讲坛、坛坛是佳酿--编程大讲坛:Visual Basic核心开发技术从入门到精通...
  6. 以ABP为基础架构的一个中等规模的OA开发日志
  7. oracle将原表数据加入新建表,Oracle新建用户并将原有表结构数据迁移,修改密码...
  8. CNN 总结 模型归类
  9. markdown与latex:数学符号远远大于\gg和远远小于书写\ll
  10. node.js学习之路(1)
  11. python学习大纲-Python学习大纲,全网最清晰学习思路
  12. 怎么比对两个html列表,两个表格对比,如何快速找出不同数据!
  13. 从零开始使用最前沿技术 Kube-VIP 搭建一个高可用的 Kubernetes 集群
  14. flowable子流程实现
  15. 对于无人驾驶技术(驾驶自动化)L0-L5分级的说明
  16. 化妆品行业如何开启新零售模式?你懂了吗
  17. Oracle table move tablespace
  18. linux 使用scp命令,scp命令详解(全)
  19. Eclipse不能查看Servler源码问题
  20. super()的两个参数

热门文章

  1. 【CVE-2021-4043】Linux本地提权漏洞复现
  2. 美术课程对计算机的好处,电脑艺术设计的美术基本功重要性论文
  3. 百度云-图像处理-动漫人物
  4. 电脑键盘部分按键失灵_电脑键盘失灵怎么办 三步教你解决问题【图文】
  5. Python 数据分析微专业课程--项目实战11 中国城市资本流动问题探索
  6. 畅邮(DM Pro)-一款强悍、纯净而稳定的重量级电子邮箱客户端(支持分发、追踪)
  7. 如何提高文字和配图的整体性?总监送你7个实用方法
  8. Android开源项目以及开源库集合(持续更新中)
  9. 写个脚本批量注册网站账号
  10. 电脑查看Windows的具体过期日期