下午突然看到磁盘里的扫雷游戏,想起了以前为了学WG,费了半天劲才成功用CE找到各种基址的日子...确实,现在认为很简单的东西当时确实就是理解不了,于是我索性又玩起了扫雷,这次换一种高级点的方法
首先说一下我知道的外挂分类吧
1.脚本外挂:严格意义上讲不能算作是外挂,因为没有改写游戏数据,算是辅助吧,这种都是通过模拟按键,图像对比等方法制作的,脚本精灵等软件可以轻松做出这种类型的辅助工具.这一类外挂主要用在网络游戏中挂机,相对较安全,不过如果被游戏代{过}{滤}理商针对的话就不好说了.
2.内存外挂:通过第三方程序修改程序内存数据,实现某些功能,这种类型的挂常用于单机游戏,比如不停地把CS的子弹修改为99可以实现无限子弹.如果是网络游戏的话,重要数据都在服务器上,并且会有验证刷新,即使修改了本地别人也看不到,当然并不是说网络游戏都不可以改,CF飞天DNF地火这种类似单机游戏的奇葩改法居然可以成功,原因是为什么我也不知道.这种外挂相对来说比较危险,容易被检测,严重的会封号.
3.封包外挂:这一类外挂是针对服务器程序逻辑上进行攻击,它并没有修改本地数据,只是模拟正常客户端发送了一些特殊意义的封包,比如CF强登,就是利用了TGP的快速登录BUG,tx农民工觉得没有人能破解他的封包信息,就很草率的图方便没有二次认证密码,所以导致某高中生经过不懈努力终于找到了漏洞利用点,实现了修改封包数据强行登陆任意CF号(请勿模仿,现已坐牢),还有DNF卡人偶,也是不停地发送使用人偶的封包,对服务器进行欺骗(服务器端判断一下物品是否已被消耗就能解决,感觉这个有点不应该)
好了,言归正传,今天要弄的只是一个最简单的,从Windows XP一安装上就自带的一个小游戏 -- winmine
单机小程序,不涉及封包,至于用脚本自动扫雷有点233,最好的方法就是做内存挂,不过这也没啥好说的,找见基址调个WINAPI修改一下就行,感觉没啥技术含量,所以就再升级一步.
今天我要用注入技术,也算是内存操作.注入也分好几种:DLL注入(自己写API,生成DLL文件,然后让注入的程序调用该接口),WinHook注入(在系统消息链中强插一个自己的消息,然后执行)以及代码写入注入.
再说说看我想实现什么效果吧,首先要做的就是一个简单的功能:秒杀通关.功能上类似于QQ连连看的秒杀,或者DNF的瞬间过图等,原理都是找到关键CALL(一个游戏都是由多个函数代码构成的,这里说的关键CALL一般意义上着就是一个特殊的函数,在这里特指能触发过关事件的那个函数),然后调用即可
思路渐渐明确了,首先要找到CALL,之后我需要调用它来达到过关的效果,一般来说调用CALL这个过程就算是外挂程序的功能了,这个外挂通常来看应该是自己写的一段程序,要实现这个调用首先得注入游戏进程,然后请求远程线程注入来调用CALL,不过这种强行注入其他进程的行为通常会被各种HIPS(主动防御软件)警告.(尤其是某卫士,只要主页不是某搜,就会提示主页被恶意篡改)
DLL注入也是同理,所以为了避免这种不必要的麻烦,我决定不用第三方程序调用这个CALL了,直接把调用加载扫雷内部.
由于我并没有扫雷的源代码,所以只能考虑反汇编文件,然后用汇编加功能了.
说干就干,首先我祭出了大名鼎鼎的OllyDbg,载入winmine运行看一下.
EP明显是C++,也对,这种东西怎么会加壳加花呢,既然是DEBUG版的C++程序.可以先打开模块间的调用,看看它用了那些WINAPI
发现SetMenu了,可以知道他不是用的CreateMenu这种方式动态创建的菜单,那么也就是说该文件还包含一个res资源文件.
这样就方便一些了,我们直接用资源工具(这里用的是ResHacker)在资源文件中添加一个菜单项即可,如下图:
添加了一个菜单项,里面有子菜单"过关",ID为594
既然写了F3是快捷键,自然也要去绑定一下,不得不说一下有了这类软件改起来确实方便
好了,游戏外壳有了,下面就是在汇编代码中逆向寻找菜单处理事件的代码,然后加入ID:594的响应事件
重新回到OllyDBG,想一下如何快速的找到这些菜单响应处理代码
如果我写的话,思路是这样的--首先是一个大的循环结构,循环内部代码如下:
获取鼠标消息,判断用户是否对该窗体执行了鼠标点击操作,如果是,继续
点击了窗体后我需要判断他点击的是否是菜单的部位,如果是,继续
用户点击菜单后我需要判断他点击的是哪一个菜单,然后分发给相应的函数
相应的函数响应对应的事件.Over
以上就是用户点击一次窗体所需要进行的处理,那么如何定位到菜单单击事件就显而易见了,我只需要在主窗体消息循环上下一个条件断点,每当用户执行的操作为单击菜单项时中断下来,然后一步一步的跟进.这样就能找到我要的代码了
千言万语化为一图,原理很简单,首先我在Windows窗口中找到了扫雷主窗体句柄,然后跟随来到了WinProc,紧接着下条件断点,msg为当前消息,WM_COMMAND为鼠标点击常量,判断一下,若两者相等就是用户在点菜单了.
接下来,需要跟进分析,所以回到扫雷窗口点击一个菜单项,准备分析代码
点击关于后,游戏成功被中断,观察右下角堆栈区域能看出确实是响应菜单项的Clicked事件,并且被点击的菜单ID为593,也就是16进制的251
回想一下刚才添加的菜单项,ID是594,16进制也就是252了,正好挨着"关于"菜单的ID,一会只需要在他附近加代码即可
一步一步的往下执行代码,可以看到,到了上图的位置时,eax的值为251,且即将发生变化,而且从右侧od给出的注释来看,这很明显就是一个Switch语句,目的就是判断用户点击的是哪一个菜单项,然后执行相应的事件
之前点的"关于"菜单,ID是16进制的251,而这里处理的也恰恰是ID为211到251的相关事件,也就是说,我们只需要把新添加的作弊功能的ID加到这里面,然后给出响应即可.
这里的Switch格式有点儿意思,没接触过汇编的人可能不明白,我简单解释一下.
(数值全部是16进制的表示方法,不再赘述)
在这段代码中,他用菜单项的ID251(也就是存在EAX寄存器里的值),去减211,得到40,然后下面一行代码JE就是判断刚才的结果(40)是否等于0,如果等于,就执行xxx代码
那么这句话的意思是什么呢,我们可以换一个菜单项ID来想,如果我点击的是一个ID为211的菜单,那么这里211-211,是不是就等于0了?那么下一行的JE成功跳转,执行对应的函数
这样就好理解了,他之所以用251-211,是因为有一个ID为211的菜单项,所以要判断
那么接下来SUB EAX,0x3D,又减了一次,如果点击的是211就已经跳走,不会执行这行代码了,所以我们可以想到,这又是一个判断,这次判断的是251-211-3D是否等于0,说白了也就是判断的ID为24E的菜单项是否被点击,就是这样,简单吧
如此重复,一直到下图这里
从右面寄存器区域可以看到EAX=0了,也就是说251已经成功的减掉了251了,也证明我们点的ID要开始跳转到事件代码了
不过这里它又变成了JNZ,意思是不等于0则跳,而此时EAX=0,跳转是无法实现的,但是我们确实即将要执行菜单ID为251的响应时间了,这是怎么回事呢
事实就是,JNZ下一行的代码就是该菜单的相应事件,至于为什么之前都是JE跳到了这里突然变成JNZ,这和C++解析器的优化有关,在此不多说
我们要做的,就是在标红的JNZ这行上做手脚,添加一段自己的代码,完成如下工作
当我们当时点击的菜单值(也就是这里存在EAX里的值)-251=0时,说明点击的还是关于按钮,保持原来的代码执行
当点击的菜单之-251=1是,说明点击的是我们新加的按钮,应该跳到我们自己写的函数执行.
思路就是这个,接下来就是完成它
从图中可以看到,JNZ这里只有一行代码(该行占6个字节),肯定不够我们用的,所以还需要去找一块面积大的土地,来盖房子
终于在程序尾部找到一片空地,就在01004A5A这里写代码吧
首先要把之前的JNZ 010021A9改成JMP,01004A5A意思是无条件跳转到这里
然后返回到 01004A5A,准备完成刚才的思路
先判断点击的菜单ID-251是否=0,若等于就跳回去(也就是上图JMP下面的CALL)
JE 01001EF5
若不等于,就再让EAX-1
DEC EAX
然后继续判断是否等于0,若等于,说明点击的是我们新添加的菜单ID,让他先跳到另一个空白处准备写代码
不过这里我仿照他原来的JNZ那种格式,换了一种思路写,我先判断是否不等于0,若不等于则说明我点击的这个菜单ID比252还要大,和我们新加的这个没关系,所以直接让它跳走
JNZ 010021A9
如果等于0,就不会执行上面那个跳转,那么如果继续往下执行,就是我们菜单ID为252的函数代码了
从这里就不难看出编译器为什么把Switch的最后一行Case写成这样了,省一行跳转代码的同时,写起来也更方便了,JNZ以下全是该菜单项的处理函数,不用考虑其他的JE判断了
好了,最后要做的就是添加我们的代码,之前说的是秒杀通关,这里就直接调用秒杀CALL
找扫雷CALL比较简单,清空最高分数据,然后随便打一局,胜利之后因为之前没有最高分肯定会让你留名记录,此时用OD下DialogBoxParamW断点,堆栈回溯即可,还是不懂的可以看这里:http://bbs.pediy.com/showthread.php?p=935501
好了,回到主题,接着刚才继续写
PUSH ECX
MOV ECX, 0x0
MOV DWORD PTR DS:[0x10056CC], ECX
MOV DWORD PTR DS:[0x10056D0], ECX
MOV DWORD PTR DS:[0x10056D4], ECX
POP ECX
CALL 01001B81
CALL 01001BAA
逐行解释一下,第1行PUSH ECX是为了保护寄存器数据,因为我后面要用到这个寄存器,但是我不确定这个寄存器是否在该过程中被其他代码使用过,所以保险起见先存起来
第2行就用到的ECX了,存0进去,目的是为了将0写道下面的三行地址中
第3,4,5行 是将0写入三个不同的了内存地址,这三个内存地址分别代表"初级 中级 高级"三种模式的扫雷所花的时间,0秒通关自然是第一了
第6行,用完ECX了,把数据恢复
第7行,弹出对话框"恭喜你已破纪录,请留下姓名"
第8行,结束该局游戏
其实第7 8行可以写在一个CALL里完成,不过既然他是这么做得到,这里就得这么调用
最后的最后,我们要让代码跳回到之前拥挤的地方继续执行,否则会在草原上迷失的
JMP 010021A9
最终代码如下图:
保存后即可看到游戏效果
这只是最简单的一个小功能,为了添加这个功能前期所要做的确实比较繁琐(Assembly+Analysis+Patch),不过已经在草原上安家了,之后添置一些家具什么的也就方便了
附上练习作品的连接(winmine原+Cracked后):
http://pan.baidu.com/s/1kUayt75 密码: stce
|