一、启动游戏
1、准备好帝国时代游戏、窗口化工具(D3DWindower.exe),运行游戏
窗口化工具不是必须的,只是为了切换窗口方便。
二、打开ce,找一下资源地址并修改
2、打开cheat engine,选择游戏进程
3、先找下食物的吧
食物初始有200
valuetype选择float,value输入200,然后点击firstscan,找到39个
然后造一个农民,这时候食物就剩150了
ce里面输入150,然后nextscan,就剩一条了,太easy了(有的游戏可能需要搜更多次),双击一下地址添加到地址列表
双击一下下面列表中的value列,修改值为15000
回到游戏发现已经生效了
同样的方式很容易就可以找到其他几个资源。
然后就可以爽一局了,但是也只有一局,因为游戏重新开始后,地址通常就变了,还要再找一次。
这个问题就要通过基址+偏移的方式来解决了。
三、找到基址和偏移
右键食物的地址列,点击“find out what writes to this address”
打开一个列表,只有一条,双击一下,看到一句话“the value of the pointer needed to find this address is probably 11BA4CD0”,就是说我们要找的指针的值可能是11BA4CD0
这句话是说,某个地址的值是可能是11BA4CD0,而11BA4CD0恰好就是食物的地址;反过来说,就是我们如果知道这个地址,取他的值,就是食物的地址了;如果这个地址是个静态地址,那么我们就大功告成了,那么这个地址是多少呢?我们用11BA4CD0这个值来搜索,看看有多少个地址的值是它。
新开始一个scan,选择hex值,搜索,好巧,只有一个,这个就是我们找的地址,按照刚才说的,每次只要找到了这个地址,读他的值,就是食物的地址了。
然而经过试验发现,这个地址也是每次重启游戏就会变化的。
其实不用试验,图中可以看到这个地址是黑色的,黑色就代表是动态地址,绿色才是静态地址,我们需要的是一个静态地址。
失败了吗?没有!继续找。我们现在知道了,有且只有11B737F8这个地址保存了食物的地址,那么游戏开发者想改变食物的值,一样要通过11B737F8这个地址来找,他每次重启游戏是怎么找到11B737F8这个地址的呢?肯定是某个地址存储着这个地址,如下图,一层套一层,某个地址存储着11B737F8,11B737F8又存储着11BA4CD0,甚至更多级,我们要做的就是耐心往上溯源。
搜索一下谁保存了11B737F8这个值,然而并没有(如下图)!!!
那么这个值怎么产生的呢。。。看看谁写了这个地址(如下图),很尴尬,白板。——解释一下,这个地址确实会有人写,但是是在游戏初始化的时候,初始化之后,我们就监控不到了,那么怎么办呢,刚才说到了,初始化之后,游戏开发者想要改变食物的值,仍然需要知道这个地址,那么他虽然不会写这个地址,但是他会读啊,那么我们来看看谁读了这个地址。
看看谁读了,终于有了(如下图),某个地址存储着11B737A8,而不是11B737F8
请注意看,下面的列表中写着,EAX=11B737A8,上面写着EAX+50,EAX+50刚好是11B737F8,也就是说,游戏开发人员并不是用某个地址存了11B737F8,而是在某个地址存了11B737A8,每次他根据那个地址获取到11B737A8之后,再+50,就得到了11B737F8,哈哈,让我们发现了,这样我们就知道规律了。
来看看哪个地址存着11B737A8,我擦,有15个之多,而且全都不是静态地址,还得往上找,这就相当于往上溯源的过程中出现了岔路,只能看运气了,好在ce比较智能,往往把可能性大的放在上面,从第一个开始尝试吧。当然,如果失败了,就再回到岔路口换下一条路继续尝试就行了。
我把前三个都加到下面列表中一个一个尝试,看看谁读了这些地址。
发现后两个都是白板,没人读这俩地址,第一个打开之后有两行,分别是下面这两个图。
第一个图又回到了11B737A8这个地址,没啥意义。
第二个图出现了一个新的值1173B390,然后加上ecx*4(ecs是1)得到我们前面的1173B394
搜索1173B394得到新的地址07AF1D08
这个地址依然是一个动态地址,还得往上找。不过我们先总结一下。
07AF1D08 ==> 1173B390
1173B390+4 = 1173B394
1173B394 ==> 11B737A8
11B737A8+50 = 11B737F8
11B737F8 ==> 11BA4CD0
11BA4CD0 ==> 食物的数量
ok,我们手里的王牌是07AF1D08,用上面的方法继续战斗
07AF1CC8+40 = 07AF1D08
搜索07AF1CC8,我的天!出现了两个绿色的地址,很可能就是基址,2A06B0和3BE7A8
插入一个知识点,静态地址的真正地址并不是2A06B0这样的,双击之后可以看到是进程名+地址,也就是“Empires.exe+2A06B0”,记住就好,后面要用到。
我们再总结一下,现在除了哪个是真的基址不清楚,其他都知道了
2A06B0或3BE7A8 ==> 07AF1CC8
07AF1CC8+40 = 07AF1D08
07AF1D08 ==> 1173B390
1173B390+4 = 1173B394
1173B394 ==> 11B737A8
11B737A8+50 = 11B737F8
11B737F8 ==> 11BA4CD0
11BA4CD0 ==> 食物的数量
知道上面的流程之后,我们来写一个公式把上面这些串起来,点击“Add Address Manually”,然后勾选pointer
把上面的公式填写进来,这个是从下往上看的,最下面写基址,右边可以看到基址对应的值是07AF1CC8
然后倒数第二行,07AF1CC8+40这个地址的值是1173B390
1173B390+4这个地址的值是11B737A8
11B737A8+50这个地址的值是11BA4CD0
11BA4CD0就是食物的地址
看下这两行,值是一样的,都是15050。
说到这里,注意看一下地址列表的前四行,食物、木材、石头、黄金的地址,不难看出一个规律:
食物地址+4=木材地址
食物地址+8=石头地址
食物地址+C=黄金的地址(注意这里是十六进制,12也就是C)
那么我们可以用同样的方式写出木材的串联,只需要把最上面的格子改成4:
好了,说回到刚才2个基址的问题,基址通常只有一个,怎么确定哪个才是真正的基址,重启游戏即可。
我们现在来重启游戏,然后用ce重新附加。
可以看出,最前面的4个地址的值已经是??了,说明已经失效了。但是我们最后添加的两个串联,也就是食物和木材,依然是有效的。
我们把串联中的基址换成另一个,发现同样有效,那么说明这两个基址确实真的都可以。。
我们来改一下食物和木材的值,可以看到,立即就生效了,这样我们就不用每次都找一遍地址了。
四、编写外挂
ce算是一个开发环境,如果我们想把上面这一堆研究成果分享给小伙伴,不懂编程的他们未必能懂啊,最简单的方式当然是封装一个exe,双击就能开搞。
这里我们用C#来实现一个(ce也能直接生成一个exe,有兴趣的自行研究一下)
1、写一个方法,读取Empires进程,然后用上面的公式流程先找到食物的地址foodAddr,然后循环4次依次遍历食物、木材、石头和黄金,如果数值小于5000,就改成10000
读写内存这里用到了一个MemorySharp框架(github传送门),这个并不是关键,类似的框架有很多,也可以用原生dll
private static void findProcessAndHack_Empires()
{
var process = System.Diagnostics.Process.GetProcessesByName("Empires").FirstOrDefault();
if (process == null)
{
Console.WriteLine("未找到进程...");
Thread.Sleep(2000);
return;
}
var sharp = new MemorySharp(process);
IntPtr baseAddr = sharp.Modules.MainModule.BaseAddress;
int addr1 = sharp.Read(new IntPtr(0x2A06B0));
int addr2 = sharp.Read(new IntPtr(addr1) + 0x40, false);
int addr3 = sharp.Read(new IntPtr(addr2) + 0x4, false);
int foodAddr = sharp.Read(new IntPtr(addr3) + 0x50, false);
//IntPtr foodAddr = baseAddr + 0;
//IntPtr woodAddr = foodAddr + 4;
//IntPtr stoneAddr = woodAddr + 4;
//IntPtr goldAddr = stoneAddr + 4;
for (int i = 0; i < 4; i++)
{
try
{
IntPtr addr = new IntPtr(foodAddr) + i * 4;
float val = sharp.Read(addr, false);
//Console.WriteLine("i={0}, val={1}", i, val);
if (val < 5000)
{
sharp.Write(addr, 10000, false);
Console.WriteLine("增加资源");
}
}
catch (Exception ex)
{
Console.WriteLine("error:{0}", ex);
}
}
}
2、main方法里面每隔3秒调用一次上面的方法,加了一个try,这样即使写内存失败,也不会出错。
static void Main(string[] args)
{
Console.WriteLine("hack程序运行成功,开启游戏开搞即可。");
while (true)
{
try
{
findProcessAndHack_Empires();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Thread.Sleep(3000);
}
3、这样我们就实现了,每3秒写一次内存,只要你开着这个程序,四种资源就永远用不完,是不是比输入woodstock这种每次加1000资源的命令更爽?
五、总结
写的很啰嗦,如果你能看完,说明你真得是毅力很大,为了尽量写的详细,我把为什么这样操作都写上了。总的来说,分为几个步骤:
1、根据游戏数值的变化找地址(需要注意的是数值类型的选择,比如有的是4byte,有的是float,这个得尝试了,发现不对就换一个试试)
2、找到一次之后,就结合谁写/读了你的地址,来一步步跟踪,这是最关键的过程,也是最繁琐的过程,还带着一些运气,可能还需要稍微看一下汇编代码。我这个例子算是很简单了。找的过程中记得把偏移记录下来。
3、都找到之后就简单了,用你熟悉的语言,按照流程读写内存就可以了。C++、C#、易语言是最常用的,其他的java、nodejs、python也都可以找到对应的读写内存的库。
就酱,有问题可Q我376665005一起学习,请备注“帝国时代外挂探讨”。