如何找基址,原理是什么

CE (cheat engine)修改数值来作弊是一种很大众化的游戏作弊方式了。

尤其是CE设计的很人性化,几乎不需要任何专业知识就能用。

但是有一个问题就是,每次开游戏或者有什么变化,就要重新找内存地址。

小时候没学过编程,但是经常在网上逛,看别人怎么作弊。经常有人提到“基址”这个词。它就是用来解决上面那个问题的关键。如何找基址是写作弊器的核心部分。

有很多教人怎么找基址的教程,基本上都是一步一步,很详细的教怎么做,但是都没有讲为什么。

(废话。。受众都是小时候的我那样的,不懂编程,就想着作弊爽一把的人,讲了原理也看不懂,直接讲怎么做就行了)


现在我是正儿八经程序员了,再回过头看如何找基址,大概知道原理是什么了。


一般来说,先要找到一个内存地址,比如说你通过不断增大减小金钱,用CE搜,搜出来了代表金钱的内存地址。这个地址我们称之为 目标地址 (我瞎起的名字)。 目标地址是会变化的,每次重开游戏,甚至换个角色,换个地图,都有可能变。

所以它的可用性不强。我们需要的是一个稳定的访问方式,这个东西就是基址。

让我们从写游戏的程序员角度来思考,程序里面需要一个地方放金钱。

假设这个游戏是C/C++写的。(一般来说都是这样的,就算不是,一般来说其他语言的底层细节和C是类似的)

最简单的情况:

比如说一个非常简单的小游戏,可能它的金钱直接就是个全局变量。因为这是一个C/C++写的游戏,全局变量的位置应该是固定的。当一个程序被加载后,其映像地址被确定,比如说现在的windows,可执行文件的加载地址一般是 (囧,忘了)。 然后该全局变量就在相对于这个映像地址的某个偏移处,这个偏移是固定的。这种情况,我们在CE搜到的直接就是不变的地址了。


如果是另一种情况:

假设这个游戏有一堆全局变量,写游戏的程序员看着这一堆全局变量很发愁。他决定把代码重构一下。把所有的全局变量整合到一个结构体里面,比如说原来是 血,蓝,金钱 都直接裸体放在全局。现在搞了一个struct,把血,蓝,金钱,都弄到结构体里面,当成员。然后全局放一个这个struct的实例。这样的话,因为这个实例是全局的,它的地址不变,然后金钱在struct内的偏移不变,所以目标地址还是可以直接当基址用。


但是如果更进一步:

假如这个struct太大了,程序员决定把它动态分配,也就是说现在全局放了一个指针,指针指向了一片动态分配出来的内存,放着这个struct。现在我们可以知道,指针是全局的,它的地址不变,但是它的内容是变化的(因为是动态分配的),所以我们搜到的目标地址就是变化的了。但是如果我们每次都先找到指针(指针是全局的,它的地址固定),再找指针指向的位置,得到struct,然后金钱在相对于这个struct的固定偏移处,这样一个迂回的方式,就可以保证每次都取到正确金钱地址了。


基本上其他的都是上面这种指针式的扩展了:

比如说某游戏采用了 一关一关的 数据结构。每关都会重新搞一块儿内存,但是金钱在这块内存的固定偏移处。这样的话,我们需要找到一个东西指向 关卡 ,然后再加一个固定的偏移。

再组合一下,可能金钱不在这个关卡结构的固定偏移处,可能关卡内部一个指针,指向一个角色,然后金钱在这个角色的固定偏移处。这就是两重指针。

当然也可能更复杂。但是总是可以找到一个方式来寻找到目标地址。

可以这样想,游戏本身肯定需要一个方式来访问金钱。这个方式就是  指针 和 固定地址、固定偏移 的组合。

并且这个方式是固定的。既然如此,我们就可以用同样的方式来访问金钱。

(一个相关的问题。以前想过,如果游戏本身加入了随机因素,那么访问方式就不是固定的了。会不会导致我们找不到基址。想了想应该不会。所谓随机,如何随机呢。举个例子,某游戏,在开启时产生一个随机数,然后在一个数组的该随机数偏移处存放金钱。这样我们可以找到存放这个随机数的基址,然后找到存放数组位置的基址,组合起来其实还是 指针和固定地址、固定偏移的组合。只不过这个固定地址(也就是这个随机数)的获得,也需要搞一遍基址。)


原理就是这样。

对照网上的教程,给出一些小时候不明白的地方的解释。

一般来说CE找到目标地址以后,教程里会用CE找,访问此地址的代码。然后找出来一堆。从里面以某种方式选中一项,查看其反汇编。举个例子,假如说金钱放在0x00000018, 然后我们找到的相关反汇编是这样的

mov eax,[0x00001234]

mov ebx, [eax+4]     这条执行完毕后ebx=0x00000010

add [ebx+8],9    金钱增加9

这里我们就可以看到目标地址0x00000018是怎么来的。 首先访问 0x00001234,得到一个数值,把这个数值+4,当成一个地址,访问这个地址得到一个数值,这个数值是0x00000010。 后面我们发现,把它加8得到目标地址,把目标地址的内容加9,也就是金钱增加9. 

可以这样想(这是一个猜测),0x00001234是一个全局指针的地址(注意,这个地址是固定的),这个指针指向关卡的数据结构,访问0x1234得到这个指针的内容,也就是关卡的首地址。关卡首地址+4处存放了一个指针。这个指针指向了一个角色的数据结构。访问这个指针得到其内容,也就是角色的首地址。再角色内部+8处存放着金钱。


这样我们就知道 基址是  [[0x00001234]+4]+8 ,这个基址的内容就是金钱,这个基址本身就是金钱的地址。


当然这里是我编的例子,一般来说反汇编出来不会这么紧凑的写着所有关键代码。很有可能我们只能看到

add [ebx+8], 9     ebx=0x00000010

然后周围都是无关代码。

这时候教程做法是在CE里再搜 0x00000010。

为什么要这样呢,我们的目的是找 这个 0x00000010是怎么来的。

这时候我们会在CE里看到很多地方都存放有0x00000010,以某种方式找到其中一项,查看谁访问了它,再找反汇编。

这时候我们有可能看到

mov ebx, [eax+4]     这条执行完毕后ebx=0x00000010, eax=0x00004321

这就是0x00000010怎么来的。是访问 eax +4 得来的, eax=0x00004321,然后再看0x00004321怎么来的。

先搜,再看谁访问,我们又找到了

mov eax,[0x00001234] eax=0x00004321

这就是0x00004321怎么来的,是访问 0x00001234得来的。这个0x00001234哪来的?。。它就是个固定值

所以这样我们就找到了基址。

但是这样有一个很大的缺点。如果像我编的那个很紧凑的例子一样,我们一次性看到了所有相关代码,那自然好。可是如果不是这样(基本上都是这样)。我们就要看某些值是从哪来的。但是关键点就在于,我们是用CE搜的相关值,用CE查的相关代码,谁也不知道是不是驴头不对马嘴,有可能找到的不是同一套访问方式的中间代码和中间值。 这是很有几率的。不过一般来说大量值和中间代码都是类似的,比如说寄存器从eax换成了ecx,但是访问方式还是不变。这就是这个不严谨方式几乎总是能成功的一大部分原因。

(后记:多用了几次以后发现,OD查反汇编虽然理论上来说是最准的,但太费事了,万一某一条线 路上跟丢了简直崩盘,还会时不时用一个不知道哪里赋值的寄存器的值,真累。。有时候还是直接CE强搜比较方便。)


另外一个要提醒的是,有的复杂情况可能中间的+4 +8偏移不是固定值,而是 +ecx 这种变量。然后还要查这个ecx哪来的。


知道了上面这种方式的原理和缺点,就可以知道,上面这种方式完全没必要。最好还是找到目标地址后,看谁访问了,然后直接转到反汇编,看整个上下文,这样保证总是对的。比如说用CE查到目标地址,然后用OD开开,下访问断点,然后看访问的这个东西,如何访问的,就能直接找到一整套的访问方式。


然后就是看反汇编了。找到底如何访问的。

这些内容就没必要说了,看各人功力了


另外,之前我们找到的基址是[[0x00001234]+4]+8,我们的猜测是 0x00001234指向关卡,关卡+4是角色,角色+8是金钱

那我们就可以去试一试,看一看,说不定角色+12是血,+16是蓝呢?

说不定关卡+8是关卡名呢?

另外说一句,一般字符串是C字符串。也就是说是一个指针,指向一个char型数组,以0结尾,查看的时候要注意。

你可能感兴趣的:(杂谈)