第十三章:多级指针与偏移量——深入理解多级指针、手动计算偏移量、使用 CE 的“指针扫描”与“偏移量扫描”功能
说实话,很多人在逆向这条路上,就是卡在了“指针”这一关。我记得刚入行那会儿,对着一个动态地址发呆,每次重启游戏地址就变,完全摸不着头脑。后来才明白,这背后藏着的就是多级指针和偏移量的游戏。
你想想看,游戏里的角色血量、金币数量,这些数据不会凭空存在。它们被分配在内存的某个角落,但每次启动程序,系统给它们安排的“住址”都不一样。那怎么找到它们?靠的就是指针——一个指向另一个地址的地址。
13.1 什么是指针?为什么需要多级指针?
指针,说白了就是一个“地址的地址”。你找到的数值在地址A,但地址A本身又被另一个地址B指着。你改数值,得先找到B,再通过B找到A。
那为什么会有多级指针?我举个例子。游戏里有个“玩家对象”,它包含血量、坐标、装备等一堆数据。这个对象本身在内存里是动态分配的,但游戏里会有一个全局的“对象管理器”来记录它的位置。这个管理器可能又指向另一个结构体……一层套一层,就形成了多级指针链。
我在逆向一个老网游时遇到过这种情况:一个角色的金币数值,需要经过5层指针才能定位到。每次游戏更新,前几层指针的偏移量可能不变,但基址会变。这就是为什么我们需要理解多级指针。
核心概念:
- 一级指针: 一个地址,里面存的是另一个地址。
- 多级指针: 指针的指针的指针……最终指向目标数据。
- 偏移量: 从一个结构体起始地址到某个成员变量的距离(字节数)。
13.2 手动计算偏移量——从基础开始
手动计算偏移量,是每个逆向工程师的基本功。别急着依赖工具,先自己走一遍流程,你才能真正理解背后的逻辑。
假设我们找到了一个动态地址 0x12345678,里面存着血量值。我们想找到它的基址。怎么做?
- 找出谁在访问这个地址: 在CE里右键这个地址,选择“找出是什么访问了这个地址”。
- 查看汇编代码: 你会看到类似
mov eax, [ecx+0x10]这样的指令。这里的ecx就是上一级指针,0x10就是偏移量。 - 追踪上一级: 记下
ecx的值,然后搜索这个值(作为地址)。重复这个过程,直到找到静态地址(绿色地址)。
嗯,这里要注意:偏移量通常是十六进制数。比如 0x10 就是16字节。为什么是16?因为结构体里前面可能有两个指针或整数,每个占8字节(64位程序)。
我的习惯: 手动计算时,我会在纸上画一个“指针链图”。比如:
基址 (静态) -> [偏移0x00] -> 一级指针 -> [偏移0x10] -> 二级指针 -> [偏移0x28] -> 血量值
这样一目了然,不容易乱。
13.3 使用 CE 的“指针扫描”功能
手动追踪虽然能加深理解,但遇到5层、6层指针时,效率太低了。这时候就该 CE 的“指针扫描”登场了。
这个功能的工作原理是:CE 会扫描整个内存,找出所有可能指向你当前地址的指针,然后层层回溯,直到找到静态地址。它会生成一个“指针列表”,里面包含各种可能的路径和偏移量。
具体操作步骤:
- 找到目标数值的地址(比如血量)。
- 右键 -> “指针扫描” -> “生成指针映射”。
- CE 会提示你保存一个
.PTR文件,保存即可。 - 重启游戏或程序,让地址变化。
- 再次找到新地址,右键 -> “指针扫描” -> “重新扫描指针映射”。
- 加载之前保存的
.PTR文件,CE 会对比两次扫描结果,筛选出仍然有效的指针路径。
我曾经踩过的坑: 指针扫描时,如果目标地址是“堆内存”中的临时对象,扫描结果可能不稳定。建议在游戏稳定运行一段时间后再扫描,避免扫描到临时指针。
13.4 偏移量扫描——更精准的定位
有时候,指针扫描的结果太多,几百上千条路径,根本不知道哪条是对的。这时候“偏移量扫描”就派上用场了。
偏移量扫描的思路是:你不是已经知道目标数值的地址了吗?CE 会尝试用不同的偏移量组合去访问这个地址,看看哪些偏移量组合能稳定地指向目标。
操作步骤:
- 找到目标地址后,右键 -> “指针扫描” -> “偏移量扫描”。
- 设置扫描参数:比如最大偏移量(通常设为 0x1000 以内)、最大层级(比如 5 层)。
- CE 会列出所有可能的偏移量组合。你只需要关注那些“静态地址”开头的路径。
我个人习惯是:先用偏移量扫描缩小范围,再用指针扫描做最终确认。两者结合,效率最高。
13.5 多级指针的实战案例
咱们来看一个实际例子。假设我们在逆向一个单机游戏,目标是找到“玩家金币”的基址。
第一步:找到金币的动态地址 0x02A1F4C0。
第二步:右键 -> “找出是什么访问了这个地址”。看到汇编指令:
mov eax, [rdx+0x18]
第三步:追踪 rdx。发现 rdx 来自 mov rdx, [rcx+0x20]。
第四步:追踪 rcx。发现 rcx 来自 mov rcx, [0x7FF6A3B0C000]。这个地址是绿色的,说明是静态基址。
所以指针链就是:
基址: 0x7FF6A3B0C000
偏移1: 0x20
偏移2: 0x18
最终: 金币值
在 CE 里添加指针时,就填:[[0x7FF6A3B0C000]+0x20]+0x18。
重要提醒: 64位程序的基址通常以 0x7FF 开头,32位程序则以 0x00 开头。这是区分程序位数的一个小技巧。
13.6 知识体系图:多级指针与偏移量核心逻辑
下面这张图展示了从动态地址到静态基址的完整追踪流程,以及指针扫描和偏移量扫描的定位逻辑:
13.7 避坑指南与个人经验
做逆向这么多年,我总结了几条关于多级指针的“血泪教训”:
- 不要迷信指针扫描: 扫描结果里可能有大量“假路径”,尤其是游戏有反调试机制时。我建议每次扫描后,手动验证前3条路径。
- 注意程序位数: 32位程序指针是4字节,64位是8字节。偏移量计算时别搞混了。
- 偏移量可能是负数: 有些结构体用负偏移访问成员,比如
[ecx-0x04]。这种情况在手动计算时容易忽略。 - 游戏更新后怎么办? 如果只是游戏版本更新,基址可能不变,但偏移量会变。这时候重新做一次偏移量扫描就行。
一个小技巧: 在 CE 里添加指针时,可以勾选“指针”选项,然后手动输入基址和偏移量。如果指针路径正确,地址会显示为绿色,数值会实时更新。这是验证指针是否正确的快速方法。
13.8 总结
多级指针和偏移量,说白了就是“通过地址找地址”的游戏。手动计算能帮你理解底层逻辑,指针扫描和偏移量扫描能帮你提高效率。两者相辅相成,缺一不可。
我记得有一次,一个朋友问我:“为什么我扫描出来的指针路径,重启游戏后就失效了?”我一看,他扫描的是堆内存里的临时对象,不是全局对象。这就是典型的“没理解指针生命周期”的问题。
所以,别光顾着用工具,多想想“这个指针是谁创建的?它什么时候被释放?”理解了这些,你才算真正掌握了多级指针。
公众号:蓝海资料掘金营,微信deep3321