hapi 发表于 2011-11-20 16:39:26

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

本帖最后由 hapi 于 2011-11-25 12:02 编辑

&id=142701]原文在此处快一年没接触智辅了。现在回来学习,哎,但师傅事情忙,又没空理我了。

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

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

众所周知,游戏更新一般那些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:,2E
005FFF56 |. A1 EC83A100 MOV EAX,DWORD PTR DS:
005FFF5B |. 6A 02 PUSH 2 ; /Arg2 = 00000002
005FFF5D |. 56 PUSH ESI ; |Arg1
005FFF5E |. 8B48 20 MOV ECX,DWORD PTR DS: ; |
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; //如果定义不正确就直接退出这个过程
止空指针的情况
    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
            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

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

li122253469 发表于 2011-11-20 20:54:39

好难懂啊 、、、、、、、、、、、、

li122253469 发表于 2011-11-20 20:54:56

你当我师父好么

li122253469 发表于 2011-11-20 20:55:09

回复 1# hapi


    你当我师父好么

hapi 发表于 2013-1-26 15:29:04

好文啊!!
页: [1]
查看完整版本: 【转】教大家写特征码扫描工具来自动寻找CALL地址