《软件逆向分析》
2022年9月
目录 {#目录 .TOC-Heading}
一、实验工具介绍
Cheat
Engine是一款专注于游戏的修改器。它可以用来扫描游戏中的内存,并允许修改它们。Cheat
Engine的用法也不是很复杂,首先是要先打开游戏,然后打开Cheat
Engine,点击左上角的那个电脑图标,在弹出来的框中选择游戏进程,然后选择扫描类型和数值类型,进行扫描并修改。内存扫描是Cheat
Engine的最主要功能之一,它可以扫描指定数值的内存地址,通过修改这些数值来达到修改游戏数据的目的,从而达到诸如无限生命、时间或弹药等优势。
二、针对"扫雷"游戏
2.1分析"初级"、"中级"和"高级"的棋盘内存地址范围
分析:扫雷有空白和数字,我们可以选择未知的初始值进行扫描,然后把重置后的数值与之前的进行比较,采用变动的数值/未变动的数值进行扫描
这里我们选择xp版的扫雷进行这个经典的试验
取消快速扫描→扫描类型→选择→未知的初始值→点击初级棋盘上第一格→进行首次扫描
1.首次扫描后已经有了很多条结果,点击"游戏",然后新开一局(前排温馨提示:按f2键可快速开启新局,效率翻倍)
2.观察第一个格子的内容,发现和上一局一样是空白
3.选择"未变动的数值",点"再次扫描"
- 当遇到第一格有数字的情况时(当出现的数字不一样时,也代表它发生了变化),证明这一格数据发生了变化
- 选择"变动的数值"
- 再次扫描
一直重复上述步骤,直到出现基址
扫描到结果后将其记录,用同样的方法找到最后一格的基址
初级棋盘第一格和最后一个地址:
中级棋盘第一格和最后一个地址:
高级棋盘第一格和最后一个地址:
可得棋盘的地址范围为:
初级棋盘 0100535E ~ 01005469
中级棋盘 0100535E ~ 01005550
高级棋盘 0100535E ~0100555E
也可以进入内存,找到对应的地址进行查看,如图所示:
2.2找出"雷数"、"笑脸"和"计时器"的内存地址
2.2.1 "雷数"的内存地址
地雷数在一局中是不会改变的,但是在改变难度的时候雷数会改变(初级:10个
中级:40个
高级:99个),可以在改变难度后搜索变化的值,在难度不变的时候搜索不变的值,最终筛选出基址。
1.点击"游戏"打开"自定义"
2.查看当前雷数
3.输入数据,开始扫描
修改雷数数值,继续搜索,最终留下三个基址,将三个数据一起拉入工作区。
我们可以尝试先找到旗子的地址。插一个旗子之后,观察三个基址,发现旗子的地址
因此可以排除上述三个基址中的一个
改变其中一个数据的数值然后新开一局,观察左上显示雷数的数值是否变为和输入的值相同。若是相同,则可以确定该地址为雷数基址(本实验中三个值查找出的地址都显示为绿色,即三个地址都是基址,所以不需要再查找每个地址的偏移值。若是查找出的地址为黑色,则必须找到该值真正的基址,通过查找"是什么访问了这个地址"或者"是什么改变了这个地址"来查找上一级地址,重复多次相同的操作后找到绿色的地址,这是查找基址的任务才完成);若不同,则继续测试下一个地址。本实验中将这三个基址都改为"1"雷数,以此可以快速验证雷数是否修改成功。
最终这里雷数内存地址为:010056A4
2.2.2 "笑脸"的内存地址
通过分析可得"笑脸"一共有四种表情,分别是在平时普通的笑脸,胜利时戴墨镜的脸,失败时悲伤的脸,点击格子时张嘴的脸。分析出这四种表情以后,就可以通过改变笑脸的表情来搜索变化的值找到控制笑脸的数值(通过编写程序的经验,对于这类只有三四个选项的变量时,往往会编写一个变量,每个值代表一个选项,而且根据经验而谈,这类flag值一般都设为个位数,即0,1,2,3…通过对扫雷的逆向分析推导出该变量的编码),找到值后,就可以找到基址。由于雷数太多导致实验时间太长,所以提前修改我们上一个找的雷数值,将其修改为2,这样可以加速试验的时间。
1.这里建议通过ce,修改为2
2.老规矩,取消快速,选择未知,点击扫描
中间的一般都是雷,可以插旗子控制输赢,或者踩雷来控制"笑脸"
开始时笑脸不变,搜索"未知的数值",在胜利时笑脸带墨镜,踩雷时表情变化,(在搜索笑脸时最好搜索胜利时的墨镜脸和失败时的悲伤脸,点击时的张口脸由于太快可能CE修改器无法捕捉到,不过可以在测试一个地址是否为笑脸基址时使用,这个数值比较少见可以代表改变表情的特征)表情变化时搜索"变化的数值",表情不变时搜索"未改变的数值"。
可以在中间表情不变的时候或者结果数量一直不变的时候,搜索几次"未变动的数值",可以筛除掉很多数据
重复多次,找到四个结果,修改结果观察笑脸表情是否变化(此处需要再次点击格子刷新),若变化,则该地址为笑脸基址;若没有变化则继续测试下一个地址。
最终找到笑脸的内存地址01005160
2.2.3 "计时器"的内存地址
计时器无法搜索精确数值,只能通过它的特性来查找,经过观察可得,计时器在不断增长,一次我们得知可以查找"增加的数值",要注意的是开局之后点击一次格子,计时器才开始计时,在重置棋盘之后到点击第一个格子之前这段时间里计数器一直为0,此时若是可选数据有很多,可以精确搜索0,来筛选数据。
搜索"未知的初始值",点击一个格子,开始计时后搜索"增加的数值",多次搜索。
找到与页面计时器同步增长的数值,那该地址即为计时器基址。
最终找到计时器的内存地址0100579C。
2.3分析雷存放算法
在整个扫雷软件的项目中,一定存在一种用于布雷的算法,它主要决定了每一局中雷所放的位置,它被主函数调用的时机也决定了扫雷游戏中存雷的算法。分析他的算法对于理解扫雷游戏的运行有很大帮助。
首先我们要知道布雷函数被主函数调用的时机,根据观察可得,打开扫雷游戏之后,自动生成初级扫雷游戏,那么软件会在这个时候就调用布雷函数将雷的位置定好了吗?咱们做一个实验:打开扫雷软件点击固定位置的一个格子,点击一个格子后就点击f2重置棋盘,再次点击上次点击的格子,多次实验。
结合之前咱们ce找各种内存地址时候的经验,我们不难发现,每开局第一次点击的格子都是安全地带,无数次试验中没有一次第一次就踩到地雷。如果主函数在最初就调用布雷函数,那么一次就踩到雷的概率应该是40/256(中级棋盘),而实际测试出的结果是0,大概率说明我们一开始的假设------认为在棋盘初始化后就已经调用布雷函数的观点是错误的。
所以我又做了第二个实验,测试第二次是否能踩到雷。和第一个实验的流程相同,同样是选取了两个固定的格子,进行了50次实验,总结实验数据后发现第二次踩雷的概率基本与理论上的概率拟合(40/255)。
甚至咱们还可以利用之前搜索到的"雷数内存地址",不能白搜是吧。
中级棋盘一共256个格子,游戏设定上限是225个雷,通过修改咱们改为255个雷!看看第一格格子会不会直接踩到。
结果可想而知。
通过实验的结果比对,可以得出一个结论:主函数在第一次点击棋盘上任意一个格子之前没有调用布雷函数,也就是说无论玩家第一次点击棋盘上任一格子都不会踩到雷,因为此时的棋盘上还不存在地雷,而在玩家点击第一个格子之后到点击第二个格子之前已经调用布雷函数,这时棋盘上的地雷才布置好。
此时我们知道了布雷函数在主函数的调用时机是在点击第一个格子之后,那么在布雷的时候就要避免将雷布置在第一个格子上,否则在玩家点击第二个格子之后会直接按踩雷结算。那么就需要在布雷函数中传递一个表示当前第一个格子的坐标的参数。我们先假设棋盘上的每一个格子位置都是由二维数组存储的,这样在传参时只需传递横坐标的值和纵坐标的值即可。
经过第二次实验我发现每次重置后地雷的布置基本上没有规律可循,我们先假设每次都是随机布置地雷位置,那么我们可以用srand函数产生随机数种子,以便在之后随机分配横纵坐标。以中级难度为例,我们需要提前知道需要多少雷数以及棋盘的长和宽,所以在调用布雷函数时也要把所需雷数和棋盘的长宽传参过来。知道雷数后设一个循环函数知道所需雷数全部布置好。同时要注意需要跳过在前文中提到的第一次点击格子位置,我所猜测的扫雷布雷算法的循环体结构如下:
其中row和col为第一次点击格子的横纵坐标,m_uMineNum为所需地雷数,m_uYNum和m_uXNum分别为棋盘的长和宽。因为随机数的产生没有限制范围,所以会出现超过已有棋盘范围的可能,为了解决这个问题,我对产生的随机数取余,使其永远在棋盘限制的范围内。对于调过第一个格子的问题,我采用条件语句加continue跳过本次循环的语句来解决,经过编译证实其与原版扫雷软件的效果相同。
现在我们已经找到了符合要求数量的雷的位置,接下来我们需要做的是埋雷,根据经验我们需要在循环体中设置一个埋雷语句。做这个工作时我们要注意随机数会出现重复数据的现象,我们不需要在已经埋好雷的位置再次埋入新雷,所以我们需要避开已经埋过雷的位置。我所编写的猜测代码如下:
ATTRIB_MINE为地雷的标识码,如果一个格子的标志值uAttrib与ATTIB_MINE相同的话,这个格子处埋着地雷。我使用了一个条件语句避开已经埋过雷的格子。将循环体的index放入这个条件语句中是因为只有找到一个没有埋过雷的格子才算埋好雷,若是将index放在条件语句之外,会导致还没有埋够所需要的雷数就已经停止循环,无法达到需求。
到这里为止猜测布雷函数的基本功能已经编写完毕,经过实验验证,用这个猜测函数做出来的扫雷游戏与原版扫雷游戏规则与功能基本相似,可以证明扫雷软件的布雷算法基本与我们所猜测的一致。
2.4利用思维导图分析"扫雷"游戏软件的工作原理(设计原理)
点击工具栏的"游戏",在等级区可以选择难度,选择初级显示的是99的棋盘和10个地雷,选择中级显示的是1616的棋盘和40个地雷,选择高级显示的是16*30的棋盘和99个地雷,选择自定义可以弹出一个弹窗自主输入长度、宽度和雷数。
点击工具栏的"游戏",取消"颜色"的选择,整个界面变为灰白色。
点击工具栏的"游戏",选择"声音",玩游戏时音效出现。
点击工具栏的"游戏",选择"扫雷英雄榜",可以查看每个难度最高纪录,选择左下角"重新计数",所有记录清零。
点击工具栏的"游戏",选择开局,重置棋盘。左键点击棋盘任意一个格子给这个格子插上旗子,同时显示的雷数变少,当所有雷所在的格子被插上旗子,则判断胜利。
右键点击棋子任意一个格子,如果是第一次点击格子,不会踩到雷。之后再点击格子,如果这个格子里是雷,那么判定失败;如果这个格子周围8个格子中有雷,那么显示周围雷的个数(只有1,2,3这三种可能);如果这个格子周围无雷,自动打开周围8个格子。若是周围8个格子的外围有雷则隐藏含雷的格子,打开其他无雷的格子,以此类推。
三、针对"植物大战僵尸"游戏
3.1安装"植物大战僵尸",完成详细逆向复现报告
为了方便版本切换,这里我选择网络上的植物大战僵尸绿色版,解压即所得。方便快捷。
3.1.1针对"阳光值"
要求采用3种以上的方法扫描至少3关的"阳光值"的内存地址,并能修改;
第一种:精确数值修改阳光值
"精确数值"搜索初始阳光150
种下豌豆射手,消耗阳光到50,再次搜索,只留下一个地址
修改为999,游戏阳光值发生变化,验证成功,确定为阳光的内存地址
第二种:根据增加的数值或减少的数值改变阳光数
首先搜索阳光初始值,结果如图
种下一个植物,选择"减少的数值",再次搜索,只存在一个结果
修改为666,游戏阳光值发生变化,验证成功,确定为阳光的内存地址
第三种:根据数值增加了…或数值减少了…改变阳光数
老规矩,精确值搜索,然后种下一颗100阳光的"豌豆射手"!
搜索类型选择"数值减少了…"100,然后再次扫描
修改为888,游戏阳光值发生变化,验证成功,确定为阳光的内存地址
3.1.2找到阳光值的内存基址,简述如何分析和找出基址的过程
以上题为例,已经改好的一次阳光值为888,在此基础上找基址,如上图所示,内存地址"1CB66A68"就是存放阳光的地址,我们可以随意的修改此内存中的数值。但是此地址是动态的,下次运行游戏时它存放的肯定就不是阳光了,我们要继续查找不会变的静态地址。
1.在地址栏中,选中该行,点击鼠标右键,选择点击"找出是什么访问了这个地址"
2.将出现确认框,默认"Yes"。点击确认Yes后将会出现有汇编代码的窗口,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-538xXguP-1668256101901)(./media/image39.png)]
3.里面可能是空白的;切换回游戏,随便种一棵植物(目的是让阳光变少,好找出哪些代码修改了此内存)﹔再回到CE,会看到刚才的空白对话框中已经有内容了
4.拾取一个阳光,阳光增加,发现多了三条指令
5.选中最后一行代码,并点击"详细信息",将会出现一个新的窗口,窗口中的信息可以复制粘贴。咱们将偏移量记录好。
分析:在详细的信息中展示的是汇编语言的实现过程,[]中的值表示的是内存偏移地址,因此[]的出现表示的是内存的出现。即阳关的地址存放在内存单元中。上图中我们可以得知add
[eax+5560],ecx这条指令是加法运算,最右侧ECX里面就是我们当前需要增加的阳光数,将ECX中的阳光数赋值给[eax+5560]这个内存地址,那么我们的阳光就会增加,此时我们需要知道EAX寄存器指向的地址是多少,CE中已经为我们分析出了EAX寄存器当前值是1CC3D388我们此时需要记下它的一级偏移5560,然后去搜索1CC3D388这个内存地址。
- 根据详细信息中给出的十六进制数,进行新的搜索,搜索指针数值,此次搜索的是十六进制数值所以需要勾选数值框前面的"十六进制"框
分析:通过搜索指针数值之后会发先还有很多的结果,此时我们根据所搜索到的地址的形式进行分析,在这些内存地址中以001开头的地址,以及以193开头的地址出现的数目都是很多的,这么多的地址不具有特殊性,因此它们不会是阳光的基地址。然而此时出现了一个特殊的地址0281A368、0CDB72E4和0CDB7474,这三个地址显然是很特殊的三个地址,那么在这么多地址中,由于它们比别的地址更加具有特殊性,那么它们就最有可能又来继续搜索静态基地址。
7.根据上述的分析,我们将目光锁定到了0281A368、0CDB72E4和0CDB7474这三个最特殊的地址身上。双击将这它们地址记录到下方。
8.由于此时并没有出现绿色的静态基地址,所以我们继续搜索,右键点击,什么访问了这个地址
在这个地址下,采集阳光,我拾取了3次,这边计数符合
第二个不是
第三个地址,这里我拾取两次阳光,计数和阳光没有关系,也不是这个。
所以回到第一个地址,重新试验
拾取2次阳光,找到对应指令
通过观察什么访问了这个地址,发现箭头所指向的指令是会随着阳光的产生而发生变化。窗口中会出现一大堆指令,这里也需要一个遍历技巧,我们可以排除CMP之类的对比指令,因为我们是增加阳光所以不可能出现对比的代码,此外我们需要关注操作数左侧是EAX的,因为我们要找的是谁给EAX赋值的,我们选择mov eax,[edx+00000768]这条汇编指令,然后发现二级偏移是768。
此时我们根据给出的16进制数值进行搜索
那么这个时候我们就索搜到了绿色的静态地址,这些绿色的地址都属于静态的,到此说明我们已经找到了这个阳光的基地址了,这里我们可以随意选择绿色的地址作为基址使用。
但是还需要判断哪个是阳光的基地址?
对四个地址逐一进行分析
通过观察什么访问了这个地址发现,之后箭头所指向的指令是会随着阳光的产生而发生变化,所以第二个地址就是阳光的基地址
最后我们通过查找到的基址与偏移相加的形式,就可以定位到动态地址了,具体公式应该是阳光=
[[[006a9ec0]+768]+5560],我们可以直接在CE中添加这个指针,用于进行测试,操作如下:
点击手动添加地址,将这个特殊的地址输入到地址框中,点击下方的指针按钮,根据上面的记录,输入偏移地址,确定具体数值。然后锁定数值。
根据上述流程可以排除另外两个的偏移地址,所以02819C00中涉及到的偏移量分别为:00005560、00000768,分别点击"添加偏移"加入其中。有一个小细节是当你输入正确的基地址和偏移量后,右上角显示当前阳光数的时候,就代表你找对了
重新开始游戏确认阳光是否实现了永久锁定
综上所述,阳光的基地址是006A9F38。
3.1.3画出阳光值的内存访问示意图或工作原理;
3.1.4分析阳光值功能实现的设计算法
我们先来说一下为什么会有动态地址与基址的概念!
大部分编程语言都会有局部变量和全局变量,相对于局部变量来说是在游戏运行后动态分配的默认由堆栈存储,而全局变量则是我们所说的基址其默认存储在全局数据区,全局数据区里面的数据则是在编译的时候就写入到程序里了,所以不会变化,而游戏的开发都会使用面向对象技术,我们可以推测游戏中的阳光很可能就是类中的一个数据成员,而数据成员的地址就是通过new动态分配的,如下代码:
#include <stdio.h>
class SunClass{
public:
int SunTime;
int SunValue;
int SunAttr;
};
int main()
{
SunClass *Sun=new SunClass;
Sun->SunValue=100;
printf("SunValue: %d
",Sun->SunValue);
return 0;
}
如上代码定义了SunClass类,在主函数中我们为Sun实例指针动态分配了内存,分配的内存存储在栈中,而栈地址每次都会发生变化,所以分配的内存地址是不固定的,从而导致阳光的地址是动态的
3.1.5分析阳光值的相关汇编语言代码的工作过程
通过编程的方式读取并修改我们的阳光数量,如下这样一段代码,它可以实现读取动态地址并修改阳光数量。
#include <iostream>
#include <Windows.h>
int GetDyAddr(int Pid,int Base, int Offset[], int len)
{
int temp;
HANDLE Process;
Process = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
ReadProcessMemory(Process, (LPVOID)Base, &temp, 4, NULL);
for (int i = 0; i < len; i++)
{
if (i == len - 1)
temp += Offset[i];
else
ReadProcessMemory(Process, (LPVOID)(temp + Offset[i]), &temp, 4,
NULL);
}
return temp;
}
int main()
{
int base;
int offset[3];
int PID = 5772;
base = 0x006a9ec0;
offset[0] = 0x768;
offset[1] = 0x5560;
int addr = GetDyAddr(PID, base, offset, 2);
printf("进程地址:%x
", addr);
HANDLE Process = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
WriteProcessMemory(Process, (LPVOID)addr,&PID,4,0);
}
3.1.6 画出逆向分析的思维导图,并将以说明其逆向原理
3.1.7 分析该软件所用内存,并画图说明其数据分布
HWND的hWnd = FindWindowEx(NULL,NULL,“XXXX”,“XXXX”);/
/这里是游戏过程中,窗口的名称和窗口类名。
PVOID PADDR =(PVOID)0x43158F";
DWORD dwProcessId和= 0;
GetWindowThreadProcessId(HWND,dwProcessId和);
操作的hProcess = OpenProcess(PROCESS_VM_WRITE |
PROCESS_VM_OPERATION,FALSE,dwProcessId和) /
/适当的权限来打开游戏过程中得到处理。
和DWORD dwWritten = 0;
BYTE bCode =的0xe5;
的hProcess,PADDR,bCode,大小(bCode),与dwWritten
WriteProcessMemory();
CloseHandle的(的hProcess);
3.1.8 实现自动收集阳光,并详细说明原理
通过阳光增加的值为切入点,找到自动收集阳光的关键判断并实现自动收集阳光,首先我们猜测当阳光出现后,我们是否会去点击,这个过程必然是由一个判断和一个时钟周期事件来控制的,那么当我们点击下落的阳光以后,则该判断条件实现,会执行收集阳光的CALL,否则的话继续执行阳光下落的过场动画,这正是正向开发的一种开发手段,此时我们也仅仅是猜测,接下来我们将去验证这个大胆的想法。
为了找到阳光自动收集的关键跳转,我们需要以阳光增加作为切入点,为啥以它作为切入点呢?我们可以这样思考,当我们点击阳光后阳光增加了,说明已经完成了判断,下一步就是写入变量从而增加阳光。
那么我们先来找到阳光的动态地址,并在该动态地址上右键,选择"找出是什么改写了这个地址",然后回到游戏等待阳光出现并点击阳光,
此时CE会出现代码,点击00430A11这个内存地址,然后点击"显示反汇编程序"
右键点击设置断点,然后返回游戏 继续捡阳光
捡起阳光,阳光值增加,程序运行到断点,游戏暂停,右边加载出寄存器信息
这里add
[eax+00005560],ecx,ecx的值为19,转化为10进制为25,也就是我们捡到的一个阳光,将25添加给了[eax+00005560],所以断定这里的增加阳关的.
此时我们需要逆向思考一个问题add
[eax+0x5560],ecx这条指令是在我们阳光被点击后执行的,也就是说我们已经点击了阳光现在开始赋值了,那判断阳光是否被回收肯定是在这条指令之前出现,所以我们向上找。
观察代码我们不难看出执行add
[eax+0x5560],ecx指令之前有一个无条件跳转jmp 0x00430A0E跳过来的。
继续向上查找跳转来源,可知在jmp跳转之前有一个je
0x004309EF跳转,经过测试这个地方具体控制阳光是否增加,在向上找就到段首了,此处代码中并没有出现自动收集阳光的关键跳转,因此推断这里应该是一个控制阳光是否增加的子过程(子过程:过程中调用的过程,称为子过程),所以我们继续回朔到上一层。
点击"调试",然后"退出—执行直到返回"
找到call 00430E40,接着向上找跳转,找最近的一个jne,会看到有一个jne
00431599此处如果将其改为jmp的话即可实现自动收集阳光,也就是说如果jne跳转实现则执行收集阳光,否则继续执行阳光下落的过场动画。
找到jne代码,修改为jmp之后,就实现了自动拾取阳光的功能。
现在游戏是停止状态,我们点击运行,当拾取阳光,游戏继续暂停的时候,我们找到当时下的断点,删除他游戏就可以正常运行了。
已经实现了自动拾取阳光,原理就是:jne指的是鼠标点击阳光才能跳转到收阳光,jmp是无条件跳转,所以实现了自动拾取阳光
3.2针对"植物"
1)将植物"向日葵"的状态定义为:拿起(暗色,冷却中)为"0",放下(亮色,可以放置)为"1"。我们以植物"向日葵"为例来学习植物的冷却时间的基址扫描方法。
注意"数值类型"改为"字节"
(2)查看扫描的结果。选中后点击鼠标右键,选择"找出是什么改写了这个地址",如图所示:
因为我们要找的这个地址是特殊值,所以优先查找独一无二
特殊的地址,这里可以把大批量的21FC开头的地址过滤掉。
(3)看到窗口中出现的代码的动作和植物的操作一致。
00488E73 - C6 45 48 00 - mov byte ptr [ebp+48],00 拿起
0040CDEA - C6 44 08 70 01 - mov byte ptr [eax+ecx+70],01 放下
选中第2行代码,分析放下植物(亮色)的数据,如图所示:
(4)切换成4字节,再扫描16基址的地址指针"131A5310",同样的方法得到下面的窗口,如图所示:
(5)再扫描地址指针,分析扫描结果。选择有与植物操作有关的信息,如图所示:
(6)点击"详细信息",如图所示:
(7)再选择指针进行扫描,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YQgZ514-1668256101924)(./media/image84.png)]
- 看见绿色的地址就是静态地址。
- 多找几个植物,我们就可以发现,基地址是一样的,如图所示:
3.3针对"金币"
(1)通过cE工具收索游戏本次运行的金币基地址。猜想:金币和数字对应比例,可能是1:1,也可能是其他比例。分析:金币显示的值不是数字1:1关系,我们只能通过范围收索的方式定位金币的地址了
(2)首先未知的初始值进行扫描,如图所示:
(3)不改变金币,再次进行扫描,如图所示:
(4)变动金币,进行扫描,如图所示:
(5)重复上述步骤,直至找到极少的地址。我们可以发现。唯有一个地址与金币的数值最接近,我们双击把它放到下方地址栏中,可以知道它的比例是10:1,如图所示:
(6)像找阳光值的地址步骤一样,找到金币的基地址,如图所示:
(7)观察数据结构,找到了金币的基地址,如图所示:
3.3针对"僵尸"
3.3.1扫描僵尸的数量
(1)数僵尸的数量,进行首次扫描,如图所示:
(2)改变僵尸的数量,再次扫描,知直到扫出一个地址,如图所示:
(3)找出是什么访问了这个地址,像阳光基址一样操作,直到找出绿色的地址,如图所示:
(4)添加指针,找到僵尸的基地址,如图所示:
5.2.4.2扫描僵尸的血量
(1)首次选择未知的初始值,进行首次扫描,如图所示:
(2)被僵尸被豌豆射手打中时,进行减少的数值的扫描,如图所示:
(3)被僵尸不被豌豆射手打中时,进行不变的数值的扫描,如图所示:
(4)反复重复以上步骤,直到扫到一个地址为止,如图所示:
(3)通过分析结构,我们可以找到相关的属性,如图所示:
3.5针对"关(级别)"
(1)第一关进行,首次扫描1,如图所示:
(2)第二关进行,再次扫描2,如图所示:
(3)第三关进行,再次扫描3,如图所示:
(4)第四关进行,再次扫描4,如图所示:
(5)通过观察发现,此地址为关卡的地址。
扫描僵尸的血量
(1)首次选择未知的初始值,进行首次扫描,如图所示:
[外链图片转存中…(img-lhPbmRsa-1668256101932)]
(2)被僵尸被豌豆射手打中时,进行减少的数值的扫描,如图所示:
[外链图片转存中…(img-Yg9I8K2i-1668256101932)]
(3)被僵尸不被豌豆射手打中时,进行不变的数值的扫描,如图所示:
[外链图片转存中…(img-nB2gSUYt-1668256101933)]
(4)反复重复以上步骤,直到扫到一个地址为止,如图所示:
[外链图片转存中…(img-saYPPto2-1668256101933)]
(3)通过分析结构,我们可以找到相关的属性,如图所示:
[外链图片转存中…(img-RN0uT3HK-1668256101934)]
3.5针对"关(级别)"
(1)第一关进行,首次扫描1,如图所示:
[外链图片转存中…(img-CVJGJJYQ-1668256101935)]
(2)第二关进行,再次扫描2,如图所示:
[外链图片转存中…(img-HwedHiy2-1668256101935)]
(3)第三关进行,再次扫描3,如图所示:
[外链图片转存中…(img-Wc83SPYi-1668256101936)]
(4)第四关进行,再次扫描4,如图所示:
[外链图片转存中…(img-wUqWXcDF-1668256101936)]
(5)通过观察发现,此地址为关卡的地址。
[外链图片转存中…(img-wpcHhMPV-1668256101937)]
版权归原作者 mackilo 所有, 如有侵权,请联系我们删除。