教大家写特征码扫描工具来自动寻找CALL地址

快一年没接触外挂了。现在回来学习,哎,但师傅事情忙,又没空理我了。

闲着没事来发篇文章给各位新手,高手可以直接跳过了。

今天要说的是怎样制作自己的特征码扫描工具

众所周知,游戏更新一般那些CALL(过程或函数)都是不改变的。
(除非有什么或需要变动什么功能才会去改吧,总之极少更改。)

那么在这种条件下,我们就来写一个根据特征码来找到内存中这函数的地址。

寻找特征码的方法有两种,一是根据看哪里调用了这个函数。二是直接找这个函数体。

下面我将以诛仙的打坐CALL为例。来写一下到底要怎么实现今天这个话题。

我使用的是Delphi。所以就用Delphi来讲解了。而且本次使用指针操作。

在此我也推荐各位如果条件允许尽量使用指针操作。既快又安全。不过要注意空指针的问题。

1. 找到CALL原形

目前诛仙(356版)打坐CALL的地址是$5FFF40。

那么我们直接打开OD。CTRL + G跳转到005FFF40这个地址。

下面是我复制的打坐CALL的原形给大家看看。

005FFF40 /$ 56 PUSH ESI
005FFF41 |. 6A 02 PUSH 2
005FFF43 |. E8 88601900 CALL ElementC.00795FD0
005FFF48 |. 8BF0 MOV ESI,EAX
005FFF4A |. 83C4 04 ADD ESP,4
005FFF4D |. 85F6 TEST ESI,ESI
005FFF4F |. 74 1E JE SHORT ElementC.005FFF6F
005FFF51 |. 66:C706 2E00 MOV WORD PTR DS:[ESI],2E
005FFF56 |. A1 EC83A100 MOV EAX,DWORD PTR DS:[A183EC]
005FFF5B |. 6A 02 PUSH 2 ; /Arg2 = 00000002
005FFF5D |. 56 PUSH ESI ; |Arg1
005FFF5E |. 8B48 20 MOV ECX,DWORD PTR DS:[EAX+20] ; |
005FFF61 |. E8 6A34FDFF CALL ElementC.005D33D0 ; \ElementC.005D33D0
005FFF66 |. 56 PUSH ESI
005FFF67 |. E8 74601900 CALL ElementC.00795FE0
005FFF6C |. 83C4 04 ADD ESP,4
005FFF6F |> 5E POP ESI
005FFF70 \. C3 RETN

那么,我们怎么根据CALL原形来写特征码搜索工具呢?

请大家注意上面的机器码部分(56 6a 02 ...这部分),这个就是我们要的了。

下面我把我的函数并加了详细注释的给大家看看。


function ScanAddr: DWORD;
const
  StartAddr = $500000; //这里定义一个边界。只搜索这一部分的内存
  StopAddr = $650000; //如果超过了,那肯定就不是我们需要的了。
var
  Addr: DWORD; //保存我们函数执行的当前地址
begin
  Result := 0; //没搜索到就返回0了
  Addr := StartAddr; //从定义的StartAddr开始扫描
  if StartAddr >= StopAddr then Exit; //如果定义不正确就直接退出这个过程
  while Addr <= StopAddr do //循环读内存,每次增加一字节,看下面的Inc(Addr)
  try //注意,这里为什么要用try 而不用begin end呢?就是为了防止空指针的情况
    if (PDWORD(Addr)^ = $E8026A56) and
      //大家返回上面我们的CALL原形,仔细看看这个什么意思。
      //005FFF40 /$ 56 PUSH ESI
      //005FFF41 |. 6A 02 PUSH 2 
      //005FFF43 |. E8 88601900 CALL ElementC.00795FD0 
      //或者我换个写法,大家可能比较容易看懂 
      //if (PByte(Addr)^ = $56) and 
      // (PWORD(Addr + $1)^ = $026A) and 
      // (PByte(Addr + $3)^ = $E8) and 
      //大家都看明白了吧?注意要去跟我上面说的机器码对比一下哦。 
      //呵呵。到此。我相信大家都明白这个思路了。直接跳过这个过程。 
      (PDWORD(Addr + $8)^ = $C483F08B) and 
      (PDWORD(Addr + $C)^ = $74F68504) and 
      (PDWORD(Addr + $11)^ = $2E06C766) and 
      (PByte(Addr + $16)^ = $A1) and 
      (PDWORD(Addr + $1B)^ = $8B56026A) and 
      (PWORD(Addr + $1F)^ = $2048) and 
      (PByte(Addr + $21)^ = $E8) and 
      (PWORD(Addr + $26)^ = $E856) and 
      (PDWORD(Addr + $2C)^ = $5E04C483) and 
      (PByte(Addr + $30)^ = $C3) then 
      begin
        Result := Addr; //直到上面的条件都满足了。那结果也就出来了。
        Break; //找出结果了就退出循环。
      end
      else //如果上面的条件有一个不满足。则增加一个地址。
        Inc(Addr); //如第一次搜索的$500000不对那就跳到$500001。再不对就再跳。
  except
    Inc(Addr); //对面try 如果上面读入的过程出现异常则直接增加1
    Continue; //继续下一次循环
  end;
end;

加了注释比较花,大家可以复制到记事本把注释去掉看看。

如果大家用的ReadProcessMemory函数来读内存也没关系,方法还是一样的。

下面我就再写个过程给那些使用ReadProcessMemory读数据的新手。

//方便重用,自己先写个函数来返回内存值
function ReadMemory(dwAddress, nSize: DWORD): DWORD;
var
  BytesRead: DWORD; 
  R1: Byte;
  R2: WORD;
  R4: DWORD;
begin
  case nSize of
    1 : begin 
          ReadProcessMemory(hProcess, Pointer(dwAddress), @R1, nSize, BytesRead);
          Result := R1;
        end;
    2 : begin
          ReadProcessMemory(hProcess, Pointer(dwAddress), @R2, nSize, BytesRead);
          Result := R2;
        end;
    4 : begin
          ReadProcessMemory(hProcess, Pointer(dwAddress), @R4, nSize, BytesRead);
          Result := R4;
        end;
  end;
end;

//下面就是读取我原先的函数转换的。
function ScanAddr: DWORD;
const
  StartAddr = $500000;
  StopAddr = $650000;
var
  Addr: DWORD;
begin
  Result := 0;
  Addr := StartAddr;
  if StartAddr >= StopAddr then Exit;
  while Addr <= StopAddr do
  begin
    if (ReadMemory(Addr, 4) = $E8026A56) and
      (ReadMemory(Addr + $8, 4) = $C483F08B) and
      (ReadMemory(Addr + $C, 4) = $74F68504) and
      (ReadMemory(Addr + $11, 4) = $2E06C766) and
      (ReadMemory(Addr + $16, 1) = $A1) and
      (ReadMemory(Addr + $1B, 4) = $8B56026A) and
      (ReadMemory(Addr + $1F, 2) = $2048) and
      (ReadMemory(Addr + $21, 1) = $E8) and
      (ReadMemory(Addr + $26, 2) = $E856) and
      (ReadMemory(Addr + $2C, 4) = $5E04C483) and
      (ReadMemory(Addr + $30, 1) = $C3) then
    begin
      Result := Addr;
      Break;
    end
    else
      Inc(Addr);
  end;
end;

到此,这篇教程就写完了。我想各位都能理解了吧。上面的方法是使用函数体本身来写的,其实还是不推荐这样。

为什么呢?如果游戏公司稍微改变了代码那我们的搜索工具就也得改代码。
而且比如像寻路CALL就可以使用调用的方法来一次性找到3个地址(好象是3个吧?忘了)。

所有用到的地址。这个就当给各位新手留着做作业吧。o(∩_∩)o

最后,在此谢谢师傅长期以来对我的帮助,这个方法也是师傅教我的。就在此借花献佛一下。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
]] 使用篇 [[ 载入程序,然后分块10(刚开始应先少数量划分,先确定大范围)。起使位置最好段code,或者txt然后程序会把代段分成10块,然后从第1块开始恢复,并生成文件。生成完毕后,用杀毒软件查杀生成文件的目录清除所有带毒文件(如果杀毒软件是按顺序杀毒的话,可以在杀掉第一个文件的时候就停止杀毒,此时特征已经找到)。然后点击[二次处理]程序会自动记录第几个文件开始查到毒了,那个就是第1个特征。程序会把有特征的地方添0 并记录在右面,然后把后面的文件分10块开始从头恢复。这样不断进行(反复使用[二次处理]和杀毒)守护特征的大范围就找出了并记录在右面。 因为分为10块所以每块都比较大,这时候需要进行精确。在右面点第1个特征选择精确此特征然后此处就会被入分析器里。分块可以大一点比如100这样多次进行精确特征的范围就出来了。 关于内存复合特征定位原理和文件定位是相同的,只是用程序把生成的文件全部装载到内存中去了,然后用杀毒软件对内存进行查杀。找到报毒的文件,然后手工删除或者在特征设置中手动添加即刻。其余操作和文件定位相同。 ]] 致谢 [[ MyCCL不是突然冒出来的,都是有了前辈们的摸索才会有不断的进步。在此感谢制作CCL的作者,以及提出定位特征概念的作者。在程序编中,各位网友也给了很大的帮助。主动帮忙测试Bug和提出意见和改进之处。在此,特别要感谢[乱刀],多次给程序提出重大改进方案以及智能处理,还为我了这个简要的说明,呵呵,真是非常感谢! ]] 声明 [[ 本软件仅用作技术研究,禁止用于非法用途,否则后果自负!禁止用于商业,DIY请保留原作者信息。 ]] 常见问题 [[ 问:为什么我只找到1处特征,没有找到复合特征? 答:说明被查找程序只有一处特征.还有可能是文件划分块数过少,使程序处理得不够精确. 问:为什么用单一定位的时候定位不出特征? 答:可能检测区间当中还存在多处复合特征,必须继续使用复合定位,直到该区间只剩一组特征为止. 问:怎么在MyCCL中快速启动TK.Loader? 答:鼠标右键点击输出目录框,会弹出启动菜单. 问:生成的文件带后缀有什么用? 答:生成带后缀的文件主要是内存定位的时候程序只能装载带后缀的文件.文件定位的时候一定不要打开,否则会与某些杀毒软件冲突,定位出错误的特征. 问:怎么播放选的背景音乐? 答:Shit!这不是点唱机!!!! -___-!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值