谁能教我传奇菜鸟编辑器下载WPE无限刷元宝。RMB跪求,但是希望比太贵了啊! 本人菜鸟一个,要有点耐心哦!

查看: 5825|回复: 8
【菜鸟一只求高中手们帮助一下我,请问怎么用wpe改网页游戏。。。】
阅读权限20
在线时间 小时
求具体教程怎么修改横版格斗游戏【斗斗堂】,比如用wpe修改攻击速度,改卖出去的东西的价钱。求教啊啊。
有高手请加我qq告诉我一下谢谢了,或者直接回复就可以。
阅读权限60
在线时间 小时
人家会告诉你斗斗堂CE就可以改吗.
阅读权限99
在线时间 小时
=小菜先森= 发表于
人家会告诉你斗斗堂CE就可以改吗.
我估计你不会告诉我ce可以改
我这聪明的人听懂了
阅读权限55
在线时间 小时
都是聪明人
阅读权限60
在线时间 小时
丶小の科つ 发表于
我估计你不会告诉我ce可以改
人家就是我吖.
阅读权限60
在线时间 小时
& &貌似我也会了诶~~~~& &
阅读权限50
在线时间 小时
阅读权限20
在线时间 小时
=小菜先森= 发表于
人家会告诉你斗斗堂CE就可以改吗.
诶ce前天被和谐了,原本可以改钱买勇气水晶和体力只能现买现用,可是这周更新后就被和谐了。虽说可以改钱但是买的东西就跟没买一样,自慰啦。不过目前可以用ce改竞技场玩家生命(这个爽),还有回魔回血。
阅读权限20
在线时间 小时
<font color="# 发表于
诶ce前天被和谐了,原本可以改钱买勇气水晶和体力只能现买现用,可是这周更新后就被和谐了。虽说可以改钱 ...
所以才求封包,我先只是想求用wpe“改&攻击速度就行。
Powered by如何面对高水平的破解组织 谈暴力破解应对 - CSDN博客
如何面对高水平的破解组织 谈暴力破解应对
, 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
最近看见太多人询问内存地址的作用和使用方法,我来做个大概的解释吧& &&&内存地址,其实电脑的每个操作都会在内存中先存储,然后经CPU处理后返回内存最终显现出来,而内存里有个内存地址,是为了区分各种不同数据的,而每个地址则相对应一个数据。& & 网络游戏中,数据也会先存放到内存中,然后进行处理,包括坐标、血量、MANA等,其实所有信息都是在内存中一一存放并等待处理。处理完毕后结果将会返回原来的地址中(当然也有返回到其他的)。& & 通过以上的解释,大家可以明白一个道理,其实任何数据都是会下载到本地电脑上然后经过处理后再返回的(至于返回到网络和返回到本地就看数据的重要性了)包括网页的浏览等,任何在你电脑上可以看见的东西必定先存放到本地内存中或硬盘中等待处理后才会显现出来的。
内存和外挂:& & 说到这里当然要给大家举个例子了,就拿网络游戏来说吧。以前的网络游戏很多数据都是在本地电脑上进行处理和比对的,例如曾经的精灵,它的游戏币是在本地电脑上进行处理的,处理后的结果再返回服务器,这样就导致了当时的刷钱外挂,因为大家可以修改本地电脑上的内存地址中的数值,然后再返回给服务器。从以上可以知道单机游戏锁血锁蓝是多么简单的事了吧。因为所有的数据操作都是在本地运行计算的。& & 当然,还有的外挂是通过改变封包数据来达到效果的。例如魔兽世界加速外挂,比如我现在人物的坐标是0&&0然后按照正常速度走的话下一秒坐标是1&&1。因为魔兽世界是在本地对人物坐标进行处理,如果只是单一的改变本地游戏坐标的位置,或许你在自己电脑上会看见自己一下跑到 3&&3的位置了,但实际游戏服务器上的人物还在1&&1这个位置,那就毫无意义。我们可以截取发送坐标的封包,修改我们下一秒跨度的数值,向服务器发送我下一秒会在3&&3的位置,这时服务器处理后你的人物就在3&&3的位置了。当然,整个过程只是改变封包内的数据值,并没有向服务器多发送封包,所以有的人说是加速外挂给游戏带来了压力的说法是错误的。当然,运营商也可以通过检查你的日常数据来判断你是否使用外挂,所以是没有查不出的外挂,只有不愿意查的外挂。& & 而现在的网络游戏血、经验、等级、金钱等重要数据都是由服务器计算然后返回给客户端(就是我们),而且每次存放血、蓝等信息的内存地址在每次游戏的启动都会变化,在现在来说,修改其数值已经没有任何意义了,因为那只能让你在本地读取的数值不同而已,而真正的数据被存储在了服务器上,你无法修改。但是我们可以通过读取内存地址中的数值来精确我们脚本的计算。
脚本与读取内存地址:& & 例如我要做一个脚本,需要在血或蓝少于一个具体的值的时候加血加蓝,这时候我们就可以采用内存地址的读取,因为这种做法可以让脚本以最精确的方式去加血加蓝,而不是去看血条蓝条(其实血条蓝条这时候已经根本不重要)。在按键精灵里有这么一个脚本。VBSCall ReadMemory(内存地址:整数(16进制),类型:0-字节 1-整数 2-长整数,输出值-所读取的内容:长整型变量)
实际应用(中间的内存地址为假设):VBSCall ReadMemory( &H,xue)这句脚本的意思为,&H400000为读取内存地址400000中数值;2为读取类型,为长整数;xue则把读取出来的数值保存到xue这个变量中去。如果这个400000的地址是用来储存血量的。那我们就可以对xue这个变量来进行判断计算。实际应用如下
If xuea and y&b& &moveto 300&&500&&//以上坐标为虚拟,根据游戏不同自己更改,以下放入这个游戏里移动的移动方式的脚本endif//如果两个坐标都大于我们希望保存的地方,我们就开始向另一个方向移动
由此我们可以生成if xa and ybif x=a and y=b一共5种不同的处理方法,这样让人物回到原来的点继续开始打怪就可以轻松制作原点挂机的脚本&,
& nirvana @ 11:27 pm
说了那么多,一定会产生一个很大的问题,如何查找游戏内的内存地址。内存地址的查找& &&&我一般用金山游侠 例如我要找红,先在自己满血的时候输入上限,然后搜索,这时候会搜索出很多地址,一些是代表你当前的红,和你的上限,还有一些正好是搜索时碰巧一样的。& &&&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 eventsLPINPUT pInputs, // array of input eventsint 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 optionsDWORD dx, // horizontal position or changeDWORD dy, // vertical position or changeDWORD dwData, // wheel movementULONG_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的人用,就看大家产品定位了.好了不说了,大家努力吧!切忌不要被游戏厂商招安哦,那样有损我们的形象,我们是为了让游戏做的更好而开发的,也不愿意打乱游戏的平衡,哎,好像现在不是这样了!不说了随其自然吧!
归类于: ,
& nirvana @ 11:26 pm
  所谓游戏外挂,其实是一种游戏外辅程序,它可以协助玩家自动产生游戏动作、修改游戏网络数据包以及修改游
戏内存数据等,以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然,现在对游戏外挂程序的&合法&
身份众说纷纭,在这里我不想对此发表任何个人意见,让时间去说明一切吧。
  不管游戏外挂程序是不是&合法&身份,但是它却是具有一定的技术含量的,在这些小小程序中使用了许多高端
技术,如拦截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,newP   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,LPARAMlParam)   {   if( nCode &= 0 )   {   if( wParam == 0X79 )//当按下F10键时,激活外挂。 {  //外挂实现代码。CPoint newPoint,oldP   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 glhH}   //卸载全局钩子。   BOOL CKeyHook::Stop()   {   BOOL bResult = TRUE; if( glhHook )   bResult = UnhookWindowsHookEx(glhHook);//卸载键盘钩子。   return bR   }
  (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 = hI  //插入保存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 hereif( !bSetup ){m_hook.Start();//激活全局钩子。}else{m_hook.Stop();//撤消全局钩子。}bSetup = !bS
  (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 DLLproject。
  (2) 新建文件wsock32.h,按如下步骤输入代码:
  ① 加入相关变量声明:
   HMODULE hModule=NULL; //模块句柄   char buffer[1000]; //缓冲区   FARPROC //函数入口指针
  ② 定义指向原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,LPVOIDlpReserved)   {    if(hModule==NULL){     //加载原WinSock库,原WinSock库已复制为wsock32.001。   hModule=LoadLibrary(&wsock32.001&P);  }    else return 1;//获取原WinSock库中的所有函数的地址并保存,下面仅列出部分代码。if(hModule!=NULL){     //获取原WinSock库初始化函数的地址,并保存到WSAStartup1中。proc=GetProcAddress(hModule,&WSAStartup&);   WSAStartup1=(int (_stdcall *)(WORD,LPWSADATA))     //获取原WinSock库消除函数的地址,并保存到WSACleanup1中。    proc=GetProcAddress(hModule i,&WSACleanup&);    WSACleanup1=(int (_stdcall *)())     //获取原创建Sock函数的地址,并保存到socket1中。    proc=GetProcAddress(hModule,&socket&);     socket1=(SOCKET (_stdcall *)(int ,int,int))     //获取原创建连接函数的地址,并保存到connect1中。     proc=GetProcAddress(hModule,&connect&);     connect1=(int (_stdcall *)(SOCKET ,const struct sockaddr*,int ))     //获取原发送函数的地址,并保存到send1中。     proc=GetProcAddress(hModule,&send&);     send1=(int (_stdcall *)(SOCKET ,const char * ,int ,int ))     //获取原接收函数的地址,并保存到recv1中。     proc=GetProcAddress(hModule,&recv&);     recv1=(int (_stdcall *)(SOCKET ,char FAR * ,int ,int ))     &&其它获取函数地址代码略。   }   else return 0;   return 1;}
  ③ 定义库输出函数,在此可以对我们感兴趣的函数中添加外挂控制代码,在所有的输出函数的最后一步都调用原
WinSock库的同名函数。部分输出函数定义代码如下:
//库输出函数定义。//WinSock初始化函数。    int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATAlpWSAData)    {     //调用原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&P   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的
实现过程,至于如何加入外挂控制代码,还需要外挂开发人员对游戏数据包结构、内容、加密算法等方面的仔细分析
(这个过程将是一个艰辛的过程),再生成外挂控制代码。关于数据包分析方法和技巧,不是本文讲解的范围,如您
感兴趣可以到网上查查相关资料。
  挡截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_   //查询页信息。   VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));   //改变页保护属性为读写。   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,   PAGE_READWRITE, &mbi_thunk.Protect);   //清零。   *pData = 0&00;   //恢复页的原保护属性。   DWORD dwOldP   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 szF//被HOOK的API函数名称。   PROC pNewP//替代函数地址。   PROC pOldP//原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) hM   if(pDOSHeader-&e_magic != IMAGE_DOS_SIGNATURE) return NULL;    //检查是否为NT标志,否则返回NULL。    PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+(DWORD)(pDOSHeader-
&e_lfanew));    if(pNTHeader-&Signature != IMAGE_NT_SIGNATURE) return NULL;    //没有IAT表则返回NULL。    if(pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
return NULL;    //定位第一个IAT位置。    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)
(pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));    //根据输入库名称循环检查所有的IAT,如匹配则返回该IAT地址,否则检测下一个IAT。    while (pImportDesc-&Name)    {     //获取该IAT描述的输入库名称。   PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader +(DWORD)(pImportDesc-&Name));   if (stricmp(szCurrMod, szImportMod) == 0)   pImportDesc++;    }    if(pImportDesc-&Name == NULL) return NULL;   return pImportD   }
  再加入一个函数,用来定位被挡截API函数的IAT项并修改其内容为替代函数地址。代码如下:
   extern &C& __declspec(dllexport)   HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPIpHookApi)   //其中,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_       //查询修改页的信息。       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.F  //修改API函数IAT项内容为替代函数地址。pRealThunk-&u1.Function = (PDWORD)pHookApi-&pNewP//恢复修改页保护属性。DWORD dwOldP       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, LPCTSTRlpCaption, 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函数的*作,看看信息框是不是
有所变化。同样,如此程序正在接收网络数据包,就可以实现封包功能了。
归类于: , ,
& nirvana @ 11:24 pm
作者:不详  来源于: 外挂网
我自己做的apihook,是用了陷阱式和导入表式封装在同一个类里的。源代码还没整理,而且是用delphi编写的。本人最近忙其他一个程序,加上工作忙,所以现找来网上的一篇关于apihook的文章。本论坛很多朋友是用C++的,所以转贴了一篇C++的,原理写的蛮清楚的,用的HOOK方式是陷阱式的。PS:大名鼎鼎的WPE就是一个优秀的API Hook,怎么样?你也可以编个WPE出来:)&&
===========================利用hook截获进程的API调用&&作者:Redspider&&
截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下:HHOOK SetWindowsHookEx(int idHook, // hook typeHOOKPROC lpfn, // hook procedureHINSTANCE hMod, // handle to application instanceDWORD 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_
LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam ){DWORD dwSDWORD dwPIDWHMODULE hL
if( dwCurrentPID == 0 ){dwCurrentPID = GetCurrentProcessId();HWND hwndMainHhwndMainHook = ::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_::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_::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_GetM::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&400000jmp eax这里的0&400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例:
BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ){DWORD dwSchar szTemp[256];BOOL r =
//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 itr = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax );
// hook it again*(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetM::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize );
//Watch here after it&s executedsprintf( 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
先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,但这个我没有试验过。&
& nirvana @ 11:23 pm
原作者姓名 TopLevel
介绍本文粗略的介绍了有关API HOOK方面的一些原理,附有源代码,希望能给大家带来帮助。
读者评分 12 评分次数 4
正文Creator&&&&&&&&:&&&&TopLevelCreate Date&&&&:&&&&
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 nD
也就是说:如果我们已经挂钩好了系统函数。那么当系统进行系统调用时,首先就碰到我们的Jmp XXXX指令,而这条指令作用就是跳到我们的函数中去执行。记住挂钩系统函数时,必须(强调的说法)把我们要替换的内容保存下来,切记,切记。如果不明白,再想想刚才所说的 2。保持对系统函数的继续调用。保存这部分内容就是为了恢复系统函数入口地址的内容,让系统函数得以继续调用。我们采用的总体流程可概括如下:
& 保存系统函数入口 - 进入我们的函数 - 恢复系统函数入口 - 可以做一些我们想做的操作 - 调用系统函数 - 挂接系统函数 - 保存系统函数入口 & &
图例如下:&&&&未挂接:
XXXX = 我的函数入口指针(地址)- 系统函数入口指针(地址)- sizeof (Jmp XXXX指令的大小);
API HOOK(API钩子)的应用:
&& 屏幕取词,游戏外挂,数据包的抓取等。
&& 具体代码如下(只演示了本进程内的API HOOK).
版权所有 @
TopLevel Studio All Rights Reserved
(如有转载,劳告知一声)
特此声明:
由于在 21CN 的邮箱不太好用,作废,有新邮箱,另行通知。欢迎和大家多多交流。
我的新邮箱 :
& nirvana @ 11:22 pm
阅读本文之前,我先假设读者已经知道了 SEH 和 API Hook 的基本概念,因为我不打算在此进行扫盲工作。什么?你不懂什么叫 SEH 和 API Hook ?那&&先去找点资料看看吧,到处都有哦,推荐读物:Jeffrey Richter 大牛的《Windows核心编程》。(没话可说,研究系统底层编程的葵花宝典,必备!)
另外值得补充的是,API Hook 跟一般的 Hook 是一点关系都没有的,虽然它们都是&Hook&,但是在技术上却有着天壤之别。啊&&不明白?先去看看葵花宝典吧&&
呵呵,废话不多说了,让我们开始吧。
经常研究 Crack 的朋友一定会知道 INT 3 这个指令。(你不知道?我倒&&) 这个指令在软件调试中非常有用,因为我们可以利用它来设置特定的断点(BreakPoint),当程序遇到 INT 3 指令的时候,将会产生一个断点异常,这个异常在 Windows.inc 里面定义为 EXCEPTION_BREAKPOINT ,对应值是 h 。Hoho,说了那么多,你想到什么了吗?
是的,聪明的你应该已经想到了!既然是异常,就肯定可以通过 SEH 来进行处理。于是我们可以这样做:在调用 API 之前,先设置一个断点,然后当 API 正式运行的时候,就会因为碰到 INT 3 指令而进入我们的异常处理模块,接着我们就可以在处理模块里面为所欲为了&&是改变什么东西还是让它顺利通过,我没话说,看你喜欢吧&&
简单地说,过程就是类似这样的:
程序遇到 INT 3 指令后,产生一个中断异常,这时 Windows 就拿着一份处理异常的活挨个问 SEH 链表上的回调函数:&你干不干?&,&不干&,&你呢?&,&我也不干&&&当 Windows 终于问到我们定义好的断点异常处理函数后,他说:&让我来干好了!&,于是 Windows 就不会再问余下的人了,他把全权托给了我们的处理函数,至于我们的函数在之后做了什么手脚&&呵呵,只有天知道!
明白了吗?其实在这里我们是利用了软件调试上的一个小技巧,实现了&伪 API Hook&。严格来说,这种方法不能算是真正的 API Hook ,但是由于我们可以在 SEH 回调函数中为所欲为,而系统不会发觉,所以也可以勉强算个数吧。
弄清楚原理后,剩下的就不难了。我们首先要保存目标 API 的入口地址,接着要设置一个 INT 3 指令,然后就在 SEH 的回调函数中进行地址修正等工作,最后万事倶备,只欠东风了。程序一运行,就进入了我们的 SEH 回调函数,呵呵,你爱怎么样就怎么样吧&&
怎么样?一点都不难吧。罗里罗嗦地说了一大堆,可能有人会开始不耐烦了&&呵,别着急,下面我就给出源代码。补充一句:本方法只是提供了一种新的思路,如果你在深入研究中发现了我的错误,或者有更好的解决方法,请给我来信啊,我的邮箱:。
(注意,本技术只能在 NT/2000/XP 平台下使用)
;*********************************************************;程序名称:用 SEH 技术实现 API Hook;适用系统:Win NT/2000/XP;作者:罗聪;日期:;出处:(老罗的缤纷天地);注意事项:如欲转载,请保持本程序的完整,并注明:;转载自&老罗的缤纷天地&();*********************************************************
.386.model flat, stdcalloption casemap:none
include /masm32/include/windows.incinclude /masm32/include/kernel32.incinclude /masm32/include/user32.incincludelib /masm32/lib/kernel32.libincludelib /masm32/lib/user32.lib
WndProc&&&&&&&& proto :DWORD, :DWORD, :DWORD, :DWORDError_Handler&& proto :DWORD, :DWORD, :DWORD, :DWORDSetHook&&&&&&&& proto
.constIDI_LC&&&&&&&&&&&&&&&&& equ 1IDC_CHECKBUTTON_HOOK&&& equ 3000IDC_BUTTON_ABOUT&&&&&&& equ 3001IDC_BUTTON_EXIT&&&&&&&& equ 3002
.dataszDlgName&&&&&&&&&&&&&& db& &lc_dialog&, 0szMsgAbout&&&&&&&&&&&&& db& &-= SEH for API Hook =-&, 13, 10, 13, 10,/&&&&&&&&&&&&&&&&&&&&&&&&&&& &作者:罗聪()&, 13, 10, 13, 10,/&&&&&&&&&&&&&&&&&&&&&&&&&&& &老罗的缤纷天地&, 13, 10,/&&&&&&&&&&&&&&&&&&&&&&&&&&& &&, 13, 10, 0szMyText&&&&&&&&&&&&&&& db& 13, 10, 13, 10, &(哈哈,看到有什么不同了吗?)&, 0szMsgHooked&&&&&&&&&&&& db& &MessageBoxIndirectA() has been hooked!&,/&&&&&&&&&&&&&&&&&&&&&&&&&&& 13, 10, 13, 10,/&&&&&&&&&&&&&&&&&&&&&&&&&&& &即将改变原来的 MessageBoxIndirectA() 的参数,&, 13, 10,/&&&&&&&&&&&&&&&&&&&&&&&&&&& &请注意后面的对话框跟没有 Hook 之前有什么不同&&&, 0szCaption&&&&&&&&&&&&&& db& &SEH for API Hook by LC&, 0szLibUser&&&&&&&&&&&&&& db& &user32&P, 0szProcMsgBoxInd&&&&&&&& db& &MessageBoxIndirectA&, 0dwAddress&&&&&&&&&&&&&& dd& 0dwOldProtect&&&&&&&&&&& dd& 0bOldByte&&&&&&&&&&&&&&& db& 0dwRetAddr&&&&&&&&&&&&&& dd& 0
.data?hInstance&&&&&&&&&&&&&& HINSTANCE&&&&&& ?mbp&&&&&&&&&&&&&&&&&&&& MSGBOXPARAMS&&&szText&&&&&&&&&&&&&&&&& db& 1024 dup(?)
.codemain:&&& ; 设置 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?&&&&&&&&&&&&&&}

我要回帖

更多关于 wpe传奇刷元宝 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信