qq四国军旗2.1 beat03 builde018记牌器开发思路(三)
今天发现,四国的版本更新为18了.对应棋子内存起始值变为0x4979fc.全地址为:
//C#代码
private static readonly IntPtr baseAddresss = (IntPtr)(0x4979fc);
int address = baseAddresss.ToInt32();
for (int i = 0; i < 17; i++) {
for (int j = 0; j < 17; j++) {
land[i, j] = address;
address += 12;
Console.Write(land[i, j].ToString("x"));
Console.Write("\t");
}
Console.WriteLine();
}是一个17×17的数组,元素长度12,前4字节为棋子,中间4个字节是棋子的颜色。有朋友问,确定在客户端验证?要是客户端验证就是开发作弊器而不是记牌器了。
接着前文讲,还在找CALL(前文的地址已经不适用,但是特征码还是可以使用的。)
上文说到,在MOV EAX,DWORD PTR DS:[EBX+110]和MOV EAX,DWORD PTR DS:[4976F4(更新为496754)]之间,应该有重要的数据分析段。那么整段代码就是:
0041155F 8B83 10010000 MOV EAX,DWORD PTR DS:[EBX+110]
00411565 85C0 TEST EAX,EAX
00411567 0F84 CA010000 JE JunQiRpg.00411737
0041156D 8B0D D89A4900 MOV ECX,DWORD PTR DS:[499AD8]
00411573 85C9 TEST ECX,ECX
00411575 75 06 JNZ SHORT JunQiRpg.0041157D
00411577 894C24 14 MOV DWORD PTR SS:[ESP+14],ECX
0041157B EB 0E JMP SHORT JunQiRpg.0041158B
0041157D A1 DC9A4900 MOV EAX,DWORD PTR DS:[499ADC]
00411582 2BC1 SUB EAX,ECX
00411584 C1F8 02 SAR EAX,2
00411587 894424 14 MOV DWORD PTR SS:[ESP+14],EAX
0041158B A1 54674900 MOV EAX,DWORD PTR DS:[496754]
但是进行详细跟踪以后,无法找到内存坐标(比如棋子从498638 走到 49856C这样的数据。)而里面也有有用的数据。关键是下面几句
0041156D 8B0D D89A4900 MOV ECX,DWORD PTR DS:[499AD8]
0041157D A1 DC9A4900 MOV EAX,DWORD PTR DS:[499ADC]
00411582 2BC1 SUB EAX,ECX
00411584 C1F8 02 SAR EAX,2
发现了什么?499AD8内的值,始终不变(一个生命周期内),而499ADC内的值是其增量,可以理解为一个步进,步进大小是4字节。走一步,那就是增加8,走两步就是增加12.为什么?这个值应该是描绘其完整路径的,比如,从498638 走到 49856C,就走了一步,但是它要表示其原先的位置和现在的位置,虽然是一步,但是值明显是2.
拿到这个也没有用,但是对0041158B A1 54674900 MOV EAX,DWORD PTR DS:[496754]下面的代码认真分析以后,也无法找到地址。这就说明一个问题,起始和结束标记不是用内存地址标记的。如果是用内存地址标记,在走路时候下断,在内存中必然有起始和结束地址。
换个思路,注意到文章开头讲的17维数组没?那么起始和结束也完全可能是用数组索引来实现。找一个索引为[6,15]的棋子,走到[6,14],断点后在数据段搜索 06 15或者15 06(字节高低位可能会倒过来,这是堆栈的一个特性。)没有!!!没有搜索到,贫道郁闷了....
再思考了一下整个步骤,发现漏了一个问题,搜索的时候需要搜索16进制,也就是说,需要搜索06 0F或者0F 06,一搜索,果然存在这个值的地址。通过这样的分析,发现,走路时,起始索引在00499ac6中(16字节),结束索引在00499ac8(16字节)。就在499AD8步进地址的上面,相差1行,却要分析好长时间啊。起始和结束的C++代码大体是:
typedef struct {
byte x,
byte y
} Node;
Node from;
Node to;
那么,如果需要这两个数据的话,就有两种处理方式了,就是第一篇文章讲到的,动态内存扫描或者是HOOK API.扫描的方式容易实现,但是出错的概率也高,要想不出错,最好是一直扫描,这样精度就高,但是也可能会出现数据不同步的问题,和数据库脏数据的原理一样。而 HOOK API技术难一点,程序稳定性会更好。