多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

用CE修改器锁定绿色静态地址:从动态地址出发的基址查找实战

用CE修改器锁定绿色静态地址:从动态地址出发的基址查找实战 说实话用CE修改器的人十有八九都卡在“改完数值重启一下游戏就失效”这个坎上。我自己当年也一样对着一个动态地址折腾了一下午差点把显示器拍碎。后来搞懂了“基址”和“静态地址”这两个概念才发现之前完全是在瞎忙不管游戏重启多少次内存里那些关键的数据入口其实一直待在一个固定位置我们要做的就是顺着一条写指令把这个固定入口挖出来。今天这篇文章就专门讲怎么用CE里的“找出是什么改写了这个地址”功能一步步锁定那个稳如老狗的绿色静态地址。1. 从“数值地址”说起为什么要费劲找基址1.1 动态地址和静态地址的本质区别先说个底层逻辑。你在CE里搜到一个血值比如0x02C8FA20这个地址本身是程序运行时由系统临时分配的一块内存空间属于堆地址。堆内存的特点就是“用完可能换地方”游戏每次启动时加载顺序稍有不同同一个对象的地址就会整体漂移。这就是为什么你昨天改的地址今天打开游戏就搜不到了。而静态地址不一样它本质上是进程模块EXE或者DLL在加载时占据的一个固定偏移位置比如“游戏主模块0x00A4F2C0”。只要模块加载地址不变这个偏移就是绝对的。CE里为了直观区分把所有稳定的地址显示为绿色所以大家习惯叫“绿色静态地址”。实际上它不一定非得是绿色关键特征是重启游戏后地址不变。1.2 为什么不能用“直接改”的方式一劳永逸很多人会问我能不能直接把动态地址加进地址列表然后保存成一个.CT文件下次再打开答案是能保存但没用。因为程序重启后这个地址指向的已经不是原来那个数据了。你加载CT文件看到的数值可能是乱码甚至直接崩溃。所以真正能在重启后继续生效的修改方案必须从“静态入口”开始一层层根据偏移量找下去。这就是指针链Pointer Chain的概念。你给一个基址加上一串偏移最终就能访问到目标的内存地址。基址查找要做的事就是从一条写入指令出发反推出这串偏移和那个最终入口。2. 找基址前必须搞懂的两个命令和准备工作2.1 “找出是什么改写了这个地址”和“找出是什么访问了这个地址”有什么区别这两个命令非常容易混淆但对于找基址来说适用场景完全不同。先看图对一个地址点右键CE会给你这几个选项找出是什么改写了这个地址当程序向这个地址执行写入操作时CE会立刻中断并把当前CPU执行的指令记录下来。找出是什么访问了这个地址不管是读、写还是执行指令只要CPU访问到这个地址CE都会中断记录。找基址我们优先用“改写”而不是“访问”。因为一个游戏对象的数据会被很多地方读取比如UI显示要读血量敌人AI也要读血量唯独“写入血量”的地方相对固定且集中。把一个地址的写入点挖出来往上逆推更容易找到对象结构的创建源头。访问记录会多到让你怀疑人生尤其是那些每帧都读一遍血量的游戏几秒钟就能刷出上百条记录。2.2 打开CE并正确附加进程操作很简单但有几个细节容易被忽略。打开游戏再打开CE点击左上角的“打开进程”图标或者按CtrlAltL在进程列表里找到游戏进程注意要选带“App”或不带“App”的那个主进程不要选控制台、辅助进程或者启动器。附加进程以后CE默认会在游戏窗口上方叠加一个透明框右上角有“暂停”“加速”之类的按钮。这个框不影响操作但如果游戏是全屏模式建议先切到“无边框窗口”模式否则后面在游戏和CE之间来回切换会非常难受。2.3 附加后第一件事确认模块地址很多新手不知道CE界面左下角的“模块列表”里存着一个关键信息主程序的模块地址。你展开“查看”菜单打开“内存查看器”按CtrlAltM切换到“模块”选项卡能看到类似Game.exe 00400000 00800000 游戏主程序 Game.dll 10000000 00400000 游戏逻辑模块这个00400000就是模块基址。锁定绿色静态地址时最终结果通常就是“某个模块基址一个固定偏移”。所以提前看一眼模块列表后面分析指令时会更有方向感。3. 保姆级实操用“找出是什么改写了这个地址”锁定绿色地址3.1 场景假设与初始搜索我拿一个常见例子来演示方便你跟着操作。假设游戏里有一个“金币”变量初始值是100。我们先把CE附加到游戏进程然后数值类型选“4字节”扫描方式选“精确数值”输入100点击“首次扫描”。回到游戏里让金币变化比如捡一个金币变成101。切回CE输入101点击“再次扫描”。重复“变化-再次扫描”直到左侧扫描列表里只剩下几个地址。这时候你已经拿到了金币当前所在的动态地址比如0x0456F230。这只是开胃菜真正的重头戏是从这个地址反推静态基址。3.2 开启“改写”跟踪并触发一次写入现在对这个地址点右键选择“找出是什么改写了这个地址”。CE会弹出一个空的活动窗口标题类似“指令列表”或者“What writes to this address”。这个窗口现在是实时监听状态但先别急着回游戏。你必须让游戏对“金币”做一次写入操作。最简单的办法回游戏捡一个金币、买一件东西或者做任何能让金币数字变动的事。如果你实在找不到触发方式也可以用CE右侧的“数值改动”手动改一下当前值但这样容易触发游戏的反篡改检测所以能用游戏内操作触发就尽量用游戏内操作。触发之后切回CE你会看到那个监听窗口里多了一行记录。比如Game.exe0x0084B7E0 - 89 90 C8000000 - mov [raxC8], edx这一行意味着刚才金币值的变化是由这条指令执行的写入。指令地址是Game.exe模块的偏移0x0084B7E0写入形式是把edx的值写进“rax寄存器值C8偏移”的内存地址。3.3 从写指令中反推出对象基地址这一行记录里最值钱的信息不是指令地址而是“写入目标”。它告诉了我们两件事金币对象的存放位置是通过某个寄存器这里是rax加上一个偏移量0xC8计算出来的。如果当前金币实际地址是0x0456F230那么0x0456F230 rax 0xC8也就是rax 0x0456F168。现在问题变成了这个rax值0x0456F168是从哪里来的它是另一个对象的首地址还是某个全局变量的值我们需要继续往上追。回到刚才那条写指令的行注意CE界面里通常还有个“寄存器”信息面板它会实时显示触发中断时各个寄存器的数值。如果那行记录是在游戏运行中截获的寄存器面板里会保存当时的rax、rbx、rcx等现场值。这个rax值就是我们下一轮要追踪的“当前目标地址”。3.4 递归向上再查“谁改写了这个对象首地址”现在我们不再好奇金币本身而是把注意力放在0x0456F168这个地址上。选中这一行右键选择“找出是什么改写了这个地址”CE会给你一个新的监听窗口。这里有个易错点你要确认你选的“这个地址”是0x0456F168而不是金币直接地址0x0456F230。如果你不小心把金币地址当成目标去监听那你还会抓到刚才那条mov [raxC8]因为金币每次都是被这条指令改写的。为了正确追踪对象首地址把0x0456F168填进一个新的扫描窗口也可以但更推荐直接用右键菜单里的“找出是什么改写了这个地址”。因为这个地址之前从未被我们扫描过CE会提示“地址已添加到地址列表”或者“当前地址不可用”这时候确保地址列表里选择的是0x0456F168这项再打开监听即可。接下来重复同样的动作回游戏触发一次金币变动切回CE看记录。这次你可能会看到不同的指令比如Game.exe0x00A1A3F8 - 48 89 18 - mov [rax], rbx这条指令把rbx的值写入到了rax指向的地址。由于我们监听的是0x0456F168这个地址而rax正好等于0x0456F168所以可以推出当前对象A的地址 rax 0x0456F168但这条指令没有偏移直接把rbx整体写入。那rbx是哪来的继续看寄存器面板里rbx的值。比如rbx0x0456E500那么这就是上一层的对象地址。这里有个听上去很绕但实际操作很直接的规律每一次向上追踪都是把“目标地址 寄存器值 /- 偏移量”这个公式反代回去再把寄存器值作为新目标继续监听。3.5 多层偏移怎么叠加大多数游戏对象不是一层就到顶的。我实际抓过最夸张的例子有整整六层偏移。每一层都会在上一步的寄存器基础上增加一个偏移量。你从最底层开始往上追最后得到的是这样一条链[[[[[游戏模块0x00D0F2A0] 0x10] 0xC8] 0x0C] 0x48] 0x2C对应到CE地址列表里就是“绿色静态地址作为一个指针指向第二个对象第二个对象加偏移指向第三个对象……”依次类推。每层关系如下最外层模块基址 0xD0F2A0是绿色静态地址通常是个全局指针。[全局指针 0x10]拿到对象B的起始地址。[对象B 0xC8]拿到对象A的起始地址也就是上一小节里的0x0456F168。[对象A 0x0C]拿到金币所在结构。[结构 0x48]再[结果 0x2C]最终拿到金币的4字节数据。实际追踪时你不需要自己反复记忆这些链路CE的“指针地图”功能可以直观显示层级。但我建议你还是手动走一遍因为指针地图有时候会因为调试符号缺失而显示得很乱。3.6 如何确认你挖到的是绿色静态地址当你一层层往上追终于看到类似下面这种指令时恭喜你到头了lea rax, [Game.exe0x00D0F2A0]或者mov rdx, [Game.exe0x00A4F2C0]这类指令里的源地址是模块基址的固定偏移而不是某个动态寄存器。这意味着你追到了模块的静态数据区/全局变量区域。这个地址就是你要的“基址”。验证方法很简单记下这个地址重启游戏重新附加进程手动添加指针填入模块基址偏移然后按比例加上刚刚解析出来的各级偏移。如果最后指向的数值还是你游戏里看到的金币数那就说明链路完全正确。4. 实操中的复杂情况和高级排查技巧4.1 抓到一堆“改写指令”怎么挑重点监听窗口中一旦出现很多条指令新手往往会懵。这时候按这个优先级筛选优先看写目标含“模块名偏移”的指令这个直接指向静态基址。其次看“一个或不带偏移的寄存器存取”指令这是典型的结构体内写值。最后看那些“内存管理类”的指令比如memset、memcpy相关调用它们通常不是你要找的单变量写入点。如果写指令太多且每次触发都有多条可以先用“停止”按钮暂停监听然后手动筛选。实在拿不准某条指令是否关键就双击该指令让CE定位到反汇编窗口看看这条指令所在的函数上下文。4.2 偏移量是反着进的小心算错写指令里最常见的形式是[rcx38]、[rax-10]这种。你监听的目标地址是实际写入地址那寄存器值就是“目标地址减去偏移量”。例如指令是mov [rax-0x04], ecx实际地址是0x02A1F120那寄存器rax实际上是0x02A1F124不是0x02A1F11C。别弄反了否则指针链错一个字节CE里显示出来的数值就会是乱码或者直接把游戏搞崩。还有一种情况是写指令里直接带着基址寄存器比如mov [rbx0x10rax*4], rsi这通常是在读写数组或对象池。你监听的目标地址是一个数组元素那么“父级地址”是rbx偏移要算上0x10rax*4中间那个索引部分。这种数组场景比较复杂建议直接在CE里用“添加地址”功能把偏移写成表达式比如base0x10rax*4让CE帮你算。4.3 同一个地址被多个系统共同改写怎么办有些数值特别是主角坐标、敌人AI状态这类可能同时被物理引擎、渲染线程、网络同步等多个系统写入。监听结果会非常乱每次触发都有好几条指令。这时候别急着从“写入方”往下逆推反过来从“读取方”下手。先把该地址用右键“找出是什么访问了这个地址”切到“访问”模式然后进游戏随便走两步。访问记录里通常有一大堆读操作但其中必然有一条来自UI线程或逻辑线程的读取指令。读取指令同样能反推指针链而且往往读取链路比写入链路更简单因为它不需要考虑“值是怎么更新的”。4.4 使用“指针扫描”快速重建整个链路手动一层层监听有时候要花十几分钟如果遇到复杂结构容易中途乱掉。CE里有一个更高效的工具指针扫描。使用步骤如下找到目标动态地址后先把它加入地址列表。右键选择“指针扫描”或者“生成指针地图”弹出窗口会让你选择保存路径建议存到一个记事本里方便查看。设置扫描范围。如果不知道结构层级就勾选“多级指针”并设置最大层级比如5-8层。偏移量范围可以先用0到0xFFF后面如果没结果再扩大。点击扫描等进度条跑完。扫描结果会列出所有能到达目标地址的指针链。在结果列表里找到包含“模块名固定偏移”的条目那条就是绿色静态地址的完整链路。指针扫描的速度和你的扫描范围成正比范围太大容易跑几分钟还没结果。经验是先用小范围快速扫一遍如果发现没有符合条件的静态基址再逐步扩大。5. 常见问题速查与经验心得5.1 问题排查速查表现象可能原因解决办法监听窗口没有捕获到任何写指令数值不是实时写入或者游戏用多线程/服务端同步先触发一次明显的数值变化尝试“访问”监听检查进程是否附加正确写指令出现但寄存器面板是空的断点中断后现场已经单步执行过寄存器值被覆盖重新触发一次并在触发前打开“寄存器”视图一次性观察现场值地址列表里显示黑色重启后失效你还没追到模块基址只是堆地址继续向上监听直到出现模块名偏移的指令绿色地址加偏移后数值是乱码偏移计算错误或者指针链层级不对用指针地图核对每一层或者换用指针扫描找出标准链路数值变化很频繁写指令非常多该变量被多个逻辑系统复用改用“访问”监听通过读取指令逆推结构体地址追踪过程中游戏崩溃某些反篡改机制或调试自检关闭CE的“启用调试权限”选项或者改用VM保护模式重试5.2 锁地址前先存一份CT文件在你最终锁定绿色静态地址后最快的验证方式是点击地址列表里的地址选择“添加偏移指针”把那一串偏移逐层填进去。然后右键这个指针选择“冻结”——这个操作会把数值锁住适合测试用。等测试没问题了一定要保存成.CT文件。这样下次游戏一开你直接加载CT文件就能一键实现数值修改不需要重新查找。5.3 关于CE设置里的两个隐藏选项在CE的“设置”“调试选项”里有两个容易被忽视的选项一个是“使用VEH调试器”一个是“查询内存区域时总是读取完整数据”。如果你在监听地址时频繁出现游戏崩溃试着把VEH调试器换回默认调试器如果你监听的是超大块内存区域的写入建议把“禁用Membb浏览器的内存查询”打开否则CE会卡到怀疑人生。5.4 几个独家小技巧最后分享几个我踩坑之后总结出来的顺手操作每次触发监听之前手动保存一下游戏进度尤其是模拟经营类游戏。一旦指针算错触发崩溃还能快速读档继续干。监听窗口里的记录可以右键“复制”成文本方便你粘贴到记事本里做笔记。特别是那条关键指令和寄存器数值别只靠脑子记。如果你发现那条写指令的偏移值特别大比如大于0x1000通常意味着你监听的是对象池里的成员而不是直接的结构体字段。这时候先找找结构体里是不是有一个“指向父结构”的指针可能比单纯往上一层更省事。遇到64位游戏时注意寄存器是rax、rbx这类而不是eax、ebx。在64位游戏里有些指令会混用32位寄存器进行写入导致地址截断追踪时要额外留意指令长度。我个人在实际操作中的体会是找基址这件事本质上就是一次“从结果到源头”的逆向过程。绝大部分时间都花在理解对象结构上而不是CE的操作上。你把上面这套流程完整跑两遍基本就能形成肌肉记忆。以后再遇到同类单机游戏十分钟内把核心数值的静态地址挖出来真不是难事。
返回列表