`
cloud21
  • 浏览: 390192 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

用HOOK Call提升挂的效率和及时性

阅读更多
Lotyong 的 [轉帖]用HOOK Call提升挂的效率和及时性

来到广海也一年了,没做什么贡献(发过一篇2分文),看到不少朋友发文章帮助新手成长,确实很高兴,同时自己也比较汗颜,没怎么帮到大家。一年来,不少新朋友都已经从小菜鸟,成长到了中/高级菜鸟,也许发现自己的挂和市面上的还有一定差距,现在我来帮大家缩小这个差距。
-------------------------
以下阅读最好是具备一定基础的中级菜鸟(能熟练应用HOOK对游戏进行注入的),因为不会一一解释太过基础的词汇,请在论坛搜索相关知识。
--------------------------
下面从取游戏数据的角度来说明目前的情况
1、按键精灵,通过取色来进行,效率很低(由于找基址的普及,现在按键也用内存取值了)
2、内存挂,通过基址+偏移的方式进行,用定时轮询来保证及时性。
3、注入挂,同样通过基址+偏移方式进行,也采用定时器保证及时性,好处是内call调用方便,也省过了ReadProcessMemory读取函数,可以直取内存。
这是目前广海菜菜们的三种挂,相比以前来说,注入的开始多起来,这是好事情,说明大家水平都提高了,有人说安全性不高,注入的容易被反外挂检测到。实际上,无论是内存型,还是注入型,都有对应的API函数让反外挂检测到(像内存用的ReadProcessMemory),所以关键还是在分析反外挂检测,拦截检测函数,改变检测结果。这里顺便提一下,不是今天的主题。
我们提到了一个概念,就是及时性,为什么要保证及时性呢,外挂的一个基础功能,就是保护角色不死亡,自动加血,如果不能及时加血,那么后果,大家都很清楚。同样的,高级一些的功能,如根据怪物剩余血量选择技能进行攻击,也是对及时性要求高的。再有一点,大家能比较的,打怪效率,好的挂怪物刚死亡,就开始打下一个了,而有的挂要等一会儿,这是为什么?同样也是及时性判断。目前用定时器来判断,就有这个问题,当然你可以把定时间隔设得很短,那你一个挂要用到多少定时器?怪物血量,人物血量,背包是否满,任务是否完成等等,都需要判断。而且我们做的是内挂,游戏就占了很多资源,在多开的情况下,尽量避免浪费资源,也是一个挂是否优秀的首要条件。
也许有人说,现在电脑硬件条件好,无所谓,那我们单纯从程序角度来看,用定时轮询,是一种什么样的浪费,有没有更好的方式?有一句话,监视不如自觉,从程序上翻译,就是监视不如触发。通过定时器来监视,一般都是1ms一次,频繁的检测,却只有极低的命中率(即确实血量变化了),当然还有一种方法,就是注入到recv(收包),在每次收包时触发一次血量检测,这就是一种好的方式,大大提高了命中率。
注入到收发包函数,网上有很多相关的资料,但这还不是我推荐大家使用的方式,我们仔细分析下,游戏中很多动作都会造成收发包,包括其他人物移动,攻击,怪物移动,等等,有很多都和血量无关的,所以事实上,命中率还是很低,如何做到只在血量发生变化时调用我们的血量判断呢?这就要主角上场了,HOOK Call
进阶
现在大家有调用call的经验,自然也就有了od找call的经验,我们以一个游戏“武林”为例,在武林中,你可以通过OD断下一个地方,然后会发现,只要你受到攻击,它就会断下来。很好,这就是我们要的地方,然后我们在它断下时,查看寄存器值,eax +4,血量,eax +8 蓝量,好了,数据也有了。实际上,这和用CE找数据基址是一样的,eax的值实际上就来自于[一级基址]+偏移+[二级基址]这样的组合(和内存挂取值一样),只是由于我们已经在call的内部,所以能一下就取到值。
找到了合适的地方,接下来如何去HOOK它呢,需要注意的是,这里的HOOK,我们不需要用到API,只需要直接改汇编代码就行了(API实际上也是同样操作)。在我们断下的那个点附近,上下看看,有没有call(HOOK call嘛,肯定要找call),好,我们找到一个,我们在call的第一行下个断点,进游戏受攻击,断下来,看eax+4的值,是正确的。恭喜这个call可用,然后说明一下汇编中call的环境。
大家知道,一个call就是一个函数,可带参数,也可不带参数,参数在进call以前,被压入堆栈里,进call后从栈中取出。由于我们要HOOK call,也就是把call 0425142改为call 自己函数的地址,我们就应该像警察一样,要对现场进行保护。下面用Delphi给出一个自己函数的例子

Copy code
{人物血量信息拦截函数 }
function Char_call(): longint;
begin
  asm
    pushad  //将当前寄存器环境保存
    mov ebx,dword ptr ds:[esi+$254] //将值取出
    mov Char_ohp,ebx  //将值赋给变量
    mov ebx,dword ptr ds:[esi+$26c]
    mov Char_mhp,ebx
    mov ebx,dword ptr ds:[esi+$258]
    mov Char_omp,ebx
    mov ebx,dword ptr ds:[esi+$270]
    mov Char_mmp,ebx
    mov Char_hpChgFlag ,eax
    mov ebx,_char_c_addr //将原有call地址写入
    call ebx //执行原有的call
  end;
  Ctrl_Char_call(); //进入自己的人物处理函数(负责判断加血)
  asm
    popad  //恢复环境
    ret;  //返回
  end;
end;

大家可以看到,在这里取血量,没有用一级基址了,至于为什么血量不能直接赋给变量,要用二行代码,这就是语言的限制了。如果嵌入的是C,就可以一行搞定。最后为什么要写一个ret呢,实际上编译器在编译delphi代码后,会自动生成ret的,但有的时候,我们需要的是ret 4,或者是add esp,4,再ret,这时候就不能依靠编译器了,只能自己处理,所以我就习惯了干脆都自己处理。
接下来说一个很重要的概念“堆栈平衡”,由于我们修改了call,在我们的call里又要调用原来的call,所以必须保证堆栈里是每次call所需要的数据,否则你就会看到程序崩溃。通过od你可以看到,如果堆栈不平衡,你在返回时,将返回到别的地方去了,自然就执行不下去。HOOK call,一定要哪里来,回哪里去。
我们的call写好了,就可以通过写内存的方法,将原来的call xxxxxx,改为call 我们的函数,初学这个,需要多次调试,一不小心就会程序崩溃,所以需要你受得了打击。
使用这个技术,还需要注意你要取的值,在call前在,还是call后在,以方便你在你的函数里取值,我例子里是call前在,call后就没有了。
找HOOK call的原则:
1、越简单的call越好,也就是参数少,进call后,代码也少,更不要看到有一堆case的call。
2、层次越少越好,被你替换的call里面,最好没有别的call,这样你甚至不用调用它这个call,直接在你的代码里实现它的功能就行了。
3、参数不要有esp,如果在call里面通过esp读了参数,那就在恢复环境时比较麻烦,要做额外处理,让esp在它希望的位置。
再次进阶
也许在你使用一段时间后,你发现,你找着了关键位置,断下来命中率很高,可附近没有call,或者只有一个很麻烦的call,应该怎么办。
我们一样有办法,这就是HOOK anywhere,任意地方都可以HOOK,原理就是把普通的汇编代码,改为call 我们的函数,然后在我们的函数里执行它原有代码,再取数据。你也可以动手试一下,和HOOK call一样的写法,只是由于环境不同,需要具体处理而已。
选择语言
我个人来说,从VB开始,由于工作需要,学了JAVA和.net,可惜这二样都不能用来做外挂,因为它们是高级语言,对底层处理都自动了,这不是我们需要的。后来学了VC,VC写外挂很好很强大,可惜MFC我太不喜欢了(.net用习惯了),才改用了DELPHI。所以推荐大家用C++和Delphi来做,E和VB会很困难,基本上不要去考虑了。
总结
HOOK call极大的提升了效率,命中率很高,但同时对技术要求也高,必须要熟悉汇编的知识,容易受到打击(程序崩溃),找call来HOOK,比找call调用要简单一些,主要工作量还是在hook后的堆栈平衡调试上,有基础的朋友可以试下。
由于我这是写的介绍文,不是教程,所以代码不多,有的地方也是一句带过,没有介绍具体的实现方法,主要在引导大家,网上这方面资料确实不多,欢迎大家提问。我有时间会回答的。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics