【转】CALL是如何炼成的[更新于11.12]

CALL是如何炼成的[更新于11.12]

遇到一个CALL应该如何写?

这个是写一个内挂不可避免的问题.刚初学的朋友可能会不知道如何入手.想起刚学这方面的时候,绕过很多弯路,现在把一些经验写出来给大家参考参考吧,不是很高深的东西,但我觉得对某些人很有帮助.

CALL是什么?

CALL是汇编中的一个指令,CPU执行这条指令会执行2个动作 一:压入EIP入栈 二:跳转到后面的地址.  跟RETN指令配合就实现了汇编中子程序的作用,通常我们常说的写CALL就是 调用游戏中已经存在的功能子程序.

比如说

viod myadd (int a, int b)

{

int c=a+b;

}

这是一个简单的子程序,当我们用程序语言调用那么就是  myadd(5,4);

而在汇编里则是

push 4

push 5

call myadd

编译好的程序不会存在什么函数变量和子程序. 只有1和0 . 所以编译器会给myadd 分配一个地址.在反汇编里就是

push 4

push 5

call ********

CALL ******* 并不单单就是子程序的调用,他还可以调用 函数 API.

如函数  send

send(

socket;

buf;

len;

flags;

)

在汇编里调用就是

push flags

push len

push buf

push socket

call send

参数在反汇编里表现形式.

在汇编或反汇编里参数都是压入堆栈来供CALL调用的.而CALL以[ESP+*]的形式读取参数.

如:

viod myadd(int a,int b)

{

int c=a+b

}

myadd(4,5);

在反汇编参数 4 和参数 5 在反汇编里的实现形式是

push 5

push 4

call ********

写CALL的时候有时候会给EAX赋值而有些时候却要给ECX赋值,如何知道要给某个寄存器赋值呢?

一般学过外挂方面的知识的朋友大概都是 汇编指令 mov 的 实现的功能.

mov 操作数1 操作数2

将操作数2 的值放入到操作数1 里.

如:

mov eax,ebx

将 ebx的值放入到eax里

寄存器的作用大家都知道是用来存放数据供CPU调用.所以寄存器本身是空的.

在调用一个CALL的时候 所有的寄存器都是空的或者是调用上一个CALL遗留下来的残留数据.

当这个CALL需要一个200的值,通过ebx 储存.

那么我们调用CALL而不给ebx赋值 ,调用的时候CALL还是会读取当前ebx的值,而这个时候寄存器的值则是0或者上一个CALL调用后残留数据.而不是CALL想要的200数据.

调用CALL之所以需要寄存器,是因为CALL通过调用相关的寄存器获取到特定的数值.

而CALL调用寄存器的语句常用则有  PUSH  寄存器,    mov 寄存器,寄存器  lea 寄存器,寄存器 等等.

这里我们要提一下寄存器环境保护,众所周知,CPU的寄存器只有一个EAX ,EBX,ECX,EDX......  如果一个寄存器EAX里存放着一些资料供后面使用,但当前CALL却需要EAX储存一些临时的数值这个时候要怎么办?

这个时候我们则需要把寄存器EAX里的数值保存到一个地方,然后把EAX给CALL使用 用完后在把那个值放

回到寄存器EAX里去.  这个过程则是寄存器的环境保护. 在反汇编里保存寄存器的地方就是堆栈.

当你在一个子程序头部看到一些 push eax  而又在尾部看到 pop eax 的时候 这里的eax 就是寄存器数值保护,push eax  则是保存eax储存的数值,  pop eax则是放回去.

堆栈如何平衡?

如何理解堆栈的平衡呢?当push 压入了一个堆栈, esp的值就会-4 来存放数值. CALL完后就需要+4来把esp的值恢复回去.如何知道一个CALL是否需要堆栈平衡呢?看CALL内部的尾部就可以了, 如果CALL尾部有RETN **  或者add esp,*之类的,则是CALL自动平衡了一些堆栈,至于恢复了多少则要看指令后面带的数字.

如何写一个CALL?

其实写CALL很简单,先处理堆栈和寄存器,在处理堆栈和寄存器中的数值,最后处理堆栈平衡就可以了,

写CALL之前你要明白几个东西.

viod myadd(int a,int b)

{

int c=a+b

}

myadd(4,5);  //程序的调用代码

在反汇编里程序的调用代码为

mov eax,5

mov ebx,4

push eax

push ebx

call 5e0000  //假设 CALL的地址为5e0000

add esp,8

retn

你会如何写这个CALL呢?

写CALL最重要的一点就是把CALL需要的寄存器参数写出来.

如 这个CALL则需要2个参数.

我们的调用代码则可以这样写

push 5

push 4

call 5e0000

add esp,8

而不需要把5放入eax里 把4放入ebx里.

为何?因为CALL并没有读取eax 和ebx里的数据.

这里我们写的代码和反汇编里的代码有什么共同点呢?2者之间有和关联?

其实共同点只有三个,那就是调用了同一个地址和提供了CALL所需要的参数,还有处理了堆栈的平衡

而两者之间是没有任何关联的.

有些人认为写CALL一定要按照游戏调用CALL的代码来写.其实,不是的.游戏调用CALL的代码只是用来参考的.我们只需要写出CALL所需要的寄存器即可.  这个是很多人存在的一个误区.只要你能理解这个概念那么CALL就会变的简单明了.

所以上面我们没有按照调用代码把数值放入寄存器里而是直接压入堆栈供CALL调用.

所以说直接靠一个CALL地址(并非调用代码地址),分析代码,来写出调用代码,也并不是不可能的事情,我也可以做到一些不是很复杂的CALL代码分析,当然如果太复杂的话那就是找不自在.

记住~游戏中调用CALL的代码只是一个参考的依据.

寄存器中的值.

我认为寄存器中的值可以分为3种,

一是常量 也就是固定的值,比如说push 23123,push 0 等等

二是基址 这类一般是控件或者类的基址,或者游戏的基址,人物的基址,一般由ECX储存

三是可以自定义的值 如 坐标, 喊话频道, 喊话内容地址等等.这些都是可以自定义的.

==============================2009.11.6日更新===================

寄存器eax值的跟踪!

在调用约定里 所有CALL的返回值都是靠EAX返回的,如果EAX放不下就放在EDX里。如果返回的结果是文本类型的话就会返回一个指针地址。在游戏里,一般的功能CALL返回值很少有。

int myadd(int a, int b)

{

int c=a+b;

return c; 

}

在反汇编的表现形式大概就是

push ebp                        //寄存器环境保护

mov  ebp,esp              //将堆栈指针的值传给EBP用于开辟临时变量

push eax                          //寄存器环境保护

sub esp,4                  //开辟一个临时空间 也就是我们定义的临时变量C

mov [esp-4],0            //将 c 初始化 也就是C=0 尽管我们没有写不过编译器会自动完成

mov eax,a                //将A放入寄存器EAX里

add eax,b              //将EAX加上B

mov [esp-4],eax              // 将计算结果保存到临时变量C

mov eax,[esp-4]    //将保存结果的变量C传递到EAX

pop eax                //将寄存器保存的值取出来。

mov esp,ebp        //将堆栈指针恢复到调用CALL之前。

pop ebp              //将寄存器保存的值取出来。

retn

这个就是一个简单加法子程序的运行过程。所以很多时候我们跟踪一下自己写的程序,看看机器是如何运行你的代码的,这个是一件很有趣的事情,也会增加你对编程和汇编的了解。这里的反汇编代码是我手动写的可能和真正的有误差,但大概的过程是应该没有错的。

所以很多人在跟踪寄存器EAX的时候往往会放过挨着的CALL指令,直接找上面的汇编代码。要看看EAX是否是一个CALL的返回值其实很简单,看看运行CALL指令后EAX的值是否有变化就可以了。

CALL参数的构造。

CALL的有些参数需要自己定义,比如说常见的有,坐标,喊话频道,怪物ID等等。这些就不说了,还有一些不太明显的。比如说喊话内容。

游戏里的喊话内容是以文本指针地址存放的。有些朋友就把这个指针的基址找出来了。其实没有必要,因为对于CALL来说,他需要的只是一个喊话内容而已。而不是需要游戏里的喊话指针地址。

所以写一个CALL重要的是知道CALL需要什么,只要你能知道他需要什么你也就能够调用它了,就像机器一样你需要提供给它需要的动力才能启动。

游戏调用CALL时,值的内存传递方式。

除了堆栈和寄存器以外,游戏可能会用其他的方法传递数值,最常见的就是内存。写过游戏聊天CALL的朋友可能会遇到过频道信息放在内存里供CALL调用的。一般是在喊话CALL内部调用,用CE找到频道信息的地址吧。 当然这里既然在CALL内部调用了频道在内存里的值,也可以通过频道信息来找到喊话CALL的地址。

=================================11.11日更新.光棍节快乐===============

CALL参数的传递!

总所周知,寄存器是用来传递参数的,但本身并没有值,他只是一个存放值的空间,存放用来传递的参数.CPU执行一个CALL的时候,我们可以把几个寄存器看成是一个空的空间.那么我们就可以理解,并不是所有的寄存器都会拿来存放数据的.看CALL需要传递几个参数了.

寄存器除了传递参数外,另外一个功能就是存放临时数据,如何判断一个CALL调用了那么寄存器的参数.你要会判断这个寄存器是用来传递的还是用来临时存放数据的.

在分析CALL之前我们应该把寄存器都看成空的 除了ESP EIP2个特殊的寄存器外.

当你发现 mov eax,ebx的时候 EBX是否是CALL要调用的寄存器呢?我觉得这个要看寄存器是否有值(在假设寄存器都为空的情况下).  如果mov eax,ebx 前面没有给EBX赋值的指令的话那么 这个ebx就是CALL需要调用的寄存器.  也就是用来传递参数的寄存器. 那么EAX呢?

这里的EAX就是一个临时存放的寄存器.用来存放ebx里的数据.

如果mov eax,ebx  指令前面有给ebx赋值的话,  比如说mov ebx,123123  那么ebx也可以排除了 因为他用来存放临时数据了 .并不是用来传递数据的.

当然,在调试的情况下,调用一个CALL之前寄存器不可能为空,大部分都有之前CALL遗留下来的残留数据.

还有一种是堆栈传递.这个就不相信说了,只要看下调用CALL前的PUSH指令就能很好的得到.

寄存器esp,ebp值的跟踪!

上面说到 堆栈参数的传递,当参数压入到堆栈CALL的内部是如何读取的呢?

很多朋友都看见过这样的寻址指令 mov ebx,[esp+1c]  这个时候很多朋友都去寻找赋值 ESP 相关的指令去了,但找了半天都没找到.其实这里直接指向了堆栈. ESP是一个特殊的寄存器,他永远指向了堆栈顶部.

所以一般轻易不会去变动这个值.  当 CPU 执行 PUSH指令时 ,ESP就会-4 然后把数据存放到当前ESP的地址里.这个时候如果读取刚刚压入的参数 就是 mov ,eax,[esp]  这样堆栈顶部的数据就会被读取.

但是这个时候又压入一个数据呢?  压入的时候ESP会-4  那么 指向刚刚那个参数就是 mov ebx,[esp+4]

如果压入了5个 要指向第一个参数 就是 [esp+10]  没错就是10  一个堆栈是4字节 4个就是16字节 16在16进制里就是10

这里要说明的一下是 CALL指令也会压入一个数值.所以

PUSH EAX

CALL ********

在CALL内部指向 EAX 就是 [ESP+4]  这里其实压入了2个堆栈 ,一个是PUSH EAX  还有一个是CALL.

临时参数的表达方式是 [ebp-*]  一般是在CALL头部把ESP的数值赋值到EBP  那么 EBP永远指向CALL头部时的堆栈指针,对于下面新增的堆栈就是 以减法表示 因为 压入一个堆栈ESP就会-4  .故[EBP-*]在反汇编里代表临时变量.

=========================11.12号更新=====================================

游戏CALL分析的四种常用方法!

一般来说常见的有

发包函数下段返回:这个是比较常见的方法,代表作是完美国际的游戏.常用发包函数有send  和 WSASend 按CTRL+F9就可以返回到调用功能代码处.

功能信息跟踪:当断下发包函数返回无法找到功能CALL的话那么就需要用到这种方法了,也是比较常见的方法.比如说,自动寻路的目标坐标点. 喊话功能的喊话内容存放地址,攻击怪物的怪物ID信息,等等的一切都是.即使能靠下发包断点返回就能找到游戏功能CALL的游戏里 有些功能也需要用这种方法跟踪 最常见的是自动寻路和TAB 选怪功能.

发包BUF区下内存写入:我没用过这种方法,是听群里人说的,他在天龙里用过.断封包函数,然后在封包临时存放地址下内存写入.然后返回即可.

游戏结构分析:比较常见的是完美国际的和热血江湖,其他的也见过一些,首先找到一个CALL的地址,然后返回到他的结构点,一般是游戏的检测玩家动作的函数层, 接收到后然后对比执行相关CALL这里几乎能找到所有的动作,不过因为是最高层所以分析的东西比较多.

当然了,可能还有其他的方法,比如说下鼠标点击函数等等.

寄存器值的跟踪与分析!

有时候有的参数会经常变化 所以需要找到他存放的基址才能获取每次变化的值.这个时候就要对参数进行跟踪找到他的基址.一般是用CE 或者用OD分析代码取到.

对于这类值的跟踪我建议使用CE.最后说明一下,基址的公式并不只有一种,用最短的那个公式即可

如果某些需要用OD来跟踪的话,最好学下汇编的基础.

寄存器值的来源!

很多参数都是有来源的,比如说,喊话内容 来自于 你输入的喊话框地址,  压入的频道信息来自于你选择的频道,买卖的数量来自你选择的买卖窗口, 坐标的值来自于你点击地图生成的反馈,等等的一切都是.

看似很简单的东西,大部分人都明白.其实还有一些参数也是由一些

写CALL时常见的错误!

一般写CALL的时候常见的2种错误是,一个是无法读取一个内存地址,或者是无法写入一个内存地址,这个代码的地址一般在CALL的内部.一般由寄存器的参数赋值错误或未对需要的寄存器赋值造成的.

还有一种常见的是堆栈不平衡所造成的错误.

这2种是比较常见的错误,也非常好解决,只要对照一下正确的寄存器参数就可以了

找CALL的思路!

找CALL时,思路很重要,如果一个打坐的CALL无法用bp send返回找到, 那么该如何找呢?思路就是打坐的状态~游戏里 人物的每个动作必定有些内存值会不一样.也许这些值代表人物是在打坐状态,那么我们跟踪这个

值就很容易找到CALL了.或者说征途的买卖功能, 他的游戏是这样实现买卖的首先打开买卖窗口是不会发包

的,最后会弹出一个确认框,这个时候点确定便会发一个买卖封包.CALL内部压入了一个确认框的基址.

如果你找到这个最后确认的CALL那么之前的窗口如何寻找呢? 我觉得既然是买卖窗口最后产生的确认框地址,我们也可以通过这个地址来寻找到买卖的窗口.

一个喊话功能寻找的话并非只有查找访问喊话地址的代码,你也可以查找频道信息,密人信息等聊天有关的.所以依照这个思路,查找怪物ID也可以通过找攻击CALL来寻找.

只要你找对了正确的思路,你便可以通往一条成功的道路.

未完待续..........

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值