1. 被动分析到底在分析什么1.1 从一次真实的排查经历说起去年帮朋友看一个卡牌游戏的战斗结算逻辑他上来就想开调试器下断点我拦住了他。原因很简单那款游戏有完整性校验调试器附加的瞬间就会触发异常退出甚至连日志都不给你留。我们最后是怎么做的全程没有附加调试器没有修改一个字节的内存只靠静态反汇编加运行时日志观察就把伤害计算公式还原了个七七八八。这就是被动分析的核心价值——在目标程序完全不知情的前提下理解它的行为逻辑。它和主动分析下断点、改内存、Hook API最大的区别在于被动分析不改变程序的任何状态因此不会触发反调试、反篡改机制也不会因为你的介入而让程序行为发生偏移。很多人对游戏逆向的理解停留在“开个调试器跟一跟”的层面但实际工作中尤其是面对有保护的游戏主动手段往往是最后才用的。被动分析才是第一步也是最考验功底的一步。它要求你能从一堆汇编指令、调用关系、数据引用中像拼图一样把程序的逻辑还原出来。1.2 被动分析的三根支柱被动分析的技术体系可以拆成三块调用关系分析、交叉引用分析、数据流分析。这三者不是孤立的而是层层递进的关系。调用关系分析解决的是“谁调用了谁”的问题。一个函数被哪些地方调用它又调用了哪些函数这构成了程序的骨架。交叉引用分析解决的是“谁引用了这个数据/函数”的问题它帮你找到某个关键数据比如血量、攻击力在哪些代码里被读写。数据流分析则更进一步解决的是“数据怎么流动、怎么变换”的问题它让你理解一个值从输入到输出经历了哪些计算。打个比方调用关系是城市的道路网交叉引用是路牌和门牌号数据流是车流的方向和货物在沿途的加工过程。你只有把这三样都搞清楚才能真正理解一个程序在干什么。1.3 为什么游戏逆向特别依赖被动分析游戏和其他软件有个本质区别游戏对实时性和状态一致性极其敏感。你在一个网络游戏里下个断点游戏线程暂停几百毫秒服务器那边可能就把你踢了。你改一个内存值客户端和服务器校验不一致直接封号。所以游戏逆向天然就更依赖被动手段。另外现代游戏普遍带有反调试、反注入、代码混淆、虚拟化保护。主动分析的门槛越来越高而被动分析虽然慢但胜在安全、稳定、可重复。你分析出来的结论可以写成文档下次版本更新后对照着看哪里变了这是主动分析做不到的。2. 调用关系分析把程序的骨架摸清楚2.1 调用关系的基本概念调用关系分析说白了就是搞清楚函数之间的调用链。在逆向工程里我们通常用调用图来表示这种关系。调用图是一个有向图节点是函数边是调用关系。A调用B就有一条从A指向B的边。但实际分析中调用图往往非常庞大。一个中型游戏可能有几千上万个函数调用关系错综复杂。这时候你需要的是有重点的调用关系分析而不是把整张图都画出来。我的习惯是先找到入口点比如主循环、消息处理函数然后沿着关键路径往下追。什么叫关键路径就是和你分析目标相关的那些调用链。比如你要分析伤害计算那就从“受到伤害”这个事件的处理函数开始追。2.2 静态调用关系的提取方法静态提取调用关系主要靠反汇编器。IDA Pro、Ghidra、Binary Ninja这些工具都能自动生成调用图。但工具生成的结果有两个问题一是间接调用比如通过函数指针、虚表识别不准二是会把一些数据当代码误识别。我一般会先用工具生成一个初步的调用图然后手动修正。修正的重点是间接调用。在C写的游戏里虚函数调用非常常见。一个obj-virtualFunc()在汇编层面是call [eaxoffset]这种形式工具往往识别不出具体调的是哪个函数。这时候你需要结合对象的类型信息、虚表布局来推断。实操心得IDA的“交叉引用”功能快捷键X是分析调用关系最常用的工具。但要注意它显示的是“引用”不一定是“调用”。数据引用和代码调用要区分开。2.3 动态调用关系的观察方法静态分析有局限动态观察可以补充。但这里说的动态观察不是让你下断点而是用日志和追踪的方式。一种做法是使用API Monitor这类工具观察程序调用了哪些系统API。虽然看不到内部函数调用但能看出程序在什么时候做了什么系统级操作比如读文件、发网络包、创建线程。这些信息能帮你定位关键代码区域。另一种做法是利用程序自身的日志。很多游戏在开发模式下会输出详细的调试日志即使发布版关闭了日志你也可以通过分析日志字符串的引用来定位相关代码。比如你搜索“damage”这个字符串找到引用它的地方往往就能找到伤害计算相关的函数。2.4 调用关系分析的实际案例举个具体例子。假设你要分析一个游戏的“使用物品”功能。你可以这样做在反汇编器里搜索“use item”相关的字符串找到引用这些字符串的函数。查看这个函数的交叉引用找到调用它的地方。沿着调用链往上追找到消息处理入口。再往下追看它调用了哪些子函数比如“检查物品数量”、“扣除物品”、“应用效果”。对每个子函数重复这个过程直到你理清整条链路。这个过程听起来简单但实际操作中会遇到各种问题函数被内联了、调用被混淆了、字符串被加密了。这时候就需要结合其他分析手段比如数据流分析来辅助定位。3. 交叉引用分析找到关键数据的藏身之处3.1 交叉引用的本质交叉引用分析回答的是一个很具体的问题这个地址数据或函数在哪些地方被引用了。在逆向工程中交叉引用是你从“已知”到“未知”的桥梁。你知道一个字符串“HP”想知道哪里用到了它就查它的交叉引用。你知道一个函数被调用了想知道谁调用的也查交叉引用。交叉引用分析的核心价值在于建立关联把孤立的信息点连接成网。3.2 数据交叉引用的典型场景在游戏逆向中数据交叉引用最常见的场景是定位关键数据。比如你想找到玩家的血量值存在哪里可以这样做在游戏里观察血量的变化比如从100变成80。用内存扫描工具如Cheat Engine搜索变化的值缩小范围。找到候选地址后在反汇编器里查看这个地址的交叉引用。分析引用这个地址的代码确认哪个是真正的血量读写逻辑。但这里有个问题内存扫描属于主动分析会改变程序状态吗严格来说只读扫描不改变程序状态但某些反作弊系统会检测扫描行为。所以更安全的做法是纯静态定位通过分析游戏的数据结构布局推断出血量字段的偏移然后在反汇编器里搜索访问这个偏移的代码。3.3 函数交叉引用的分析技巧函数交叉引用分析主要用于定位关键函数。比如你想找到“计算伤害”的函数可以这样做找到伤害数值在内存中的地址通过静态分析或只读扫描。查看这个地址的交叉引用找到写入这个地址的代码。写入伤害值的代码往往就在伤害计算函数附近。查看这个函数的交叉引用确认它被谁调用。另一种方法是通过特征字符串定位。很多游戏会在代码里留下调试字符串比如“CalculateDamage”、“ApplyDamage”等。搜索这些字符串的交叉引用往往能直接定位到关键函数。3.4 交叉引用分析的注意事项交叉引用分析有几个坑要注意间接引用通过指针数组、虚表、跳转表进行的引用工具往往识别不出来。你需要手动分析这些数据结构。代码混淆有些游戏会对引用进行混淆比如把直接调用改成通过计算得出的地址调用。这时候需要结合数据流分析来还原。多级引用A引用BB引用CC才是你想要的。你需要沿着引用链多追几层。实操心得在IDA里按X查看交叉引用时注意区分“代码引用”和“数据引用”。代码引用通常是调用或跳转数据引用通常是读取或写入。不同类型的引用分析思路完全不同。4. 数据流分析理解数据的来龙去脉4.1 数据流分析要解决什么问题数据流分析的核心问题是一个数据值从哪来到哪去中间经历了什么变换。在游戏逆向中这通常意味着你要追踪一个关键值比如伤害、金币、经验的完整计算过程。举个例子你发现玩家攻击后怪物血量减少了50。你想知道这50是怎么算出来的。数据流分析就是帮你从“血量减少”这个结果反向追踪到“伤害计算”的源头。4.2 静态数据流分析的基本方法静态数据流分析主要靠反汇编代码的阅读。你需要理解每条指令对数据的影响然后沿着指令序列追踪数据的流动。以x86汇编为例常见的指令有mov dest, src把src的值复制到destadd dest, src把src加到dest上sub dest, src从dest减去srcimul dest, srcdest乘以srclea dest, [addr]把地址加载到dest追踪数据流时你需要维护一个“当前值”的概念。比如mov eax, [ebp-4] ; eax 局部变量1 add eax, 10 ; eax 局部变量1 10 mov [ebp-8], eax ; 局部变量2 eax通过这样的追踪你能知道局部变量2的值是局部变量1加10。如果局部变量1是攻击力那局部变量2就是攻击力加10。4.3 动态数据流观察方法静态分析有时会遇到困难比如代码被混淆、数据流经过太多层。这时候可以用动态方法辅助但要注意不改变程序状态。一种安全的方法是使用日志钩子。有些游戏引擎如Unity、Unreal提供了日志输出接口你可以通过分析日志字符串的引用来定位关键代码然后观察日志输出中的数值变化。另一种方法是内存快照对比。在游戏运行过程中定期对关键内存区域做只读快照然后对比不同时间点的数据变化。这种方法不修改任何内存只是读取相对安全。但要注意频繁的内存读取可能被某些反作弊系统检测到所以频率要控制。4.4 数据流分析的实际案例假设你要分析一个游戏的“经验值获取”逻辑。你可以这样做找到经验值在内存中的地址通过静态分析或只读扫描。查看写入这个地址的代码找到经验值更新的位置。沿着代码往上追看经验值是怎么计算出来的。你可能会发现类似这样的逻辑; 计算经验值 mov eax, [monster_level] ; 怪物等级 imul eax, 10 ; 等级 * 10 mov ecx, [player_level] ; 玩家等级 sub ecx, [monster_level] ; 玩家等级 - 怪物等级 add eax, ecx ; 基础经验 等级差 mov [exp_gain], eax ; 存储经验获取量通过这样的分析你就能还原出经验值的计算公式。5. 被动分析的完整工作流5.1 从目标出发制定分析计划被动分析最忌讳的就是“漫无目的地看”。你打开IDA面对几千个函数如果没有明确目标很快就会迷失。所以第一步永远是明确你要分析什么。比如你的目标是“理解战斗伤害计算”那你的分析计划可能是定位伤害数值在内存中的位置。找到写入伤害值的代码。分析伤害计算函数的输入和输出。还原伤害计算公式。验证公式的正确性。每个步骤都有明确的产出这样你才能一步步推进。5.2 信息收集与整理被动分析会产生大量信息函数地址、调用关系、数据结构、计算公式。这些信息如果不整理很快就会乱掉。我的习惯是边分析边记录用表格整理关键信息。信息类型内容地址/位置备注函数伤害计算0x00401234输入攻击力、防御力数据玩家血量0x005067894字节整数字符串Damage0x0040A000用于定位这样的表格能帮你快速回顾和验证。5.3 验证与迭代被动分析得出的结论需要验证。验证的方法不是去改代码而是观察程序行为是否符合你的推断。比如你推断伤害公式是“攻击力 - 防御力”那你就观察当攻击力增加时伤害是否增加当防御力增加时伤害是否减少。如果符合说明你的推断大概率正确。如果不符合就需要重新分析。这个过程可能需要多次迭代。每次迭代都会让你对程序的理解更深一层。6. 常见问题与排查技巧6.1 函数识别错误怎么办反汇编器有时会把数据当代码或者把代码当数据。这时候你需要手动判断。一个技巧是看指令的合理性正常的函数序言通常是push ebp; mov ebp, esp如果看到一堆无意义的指令很可能是数据被误识别了。6.2 间接调用追不下去怎么办间接调用是静态分析的难点。我的经验是先找虚表。C对象的虚表通常在对象内存的开头虚表里是一组函数指针。找到虚表后结合对象的类型信息就能推断出每个虚函数是什么。6.3 数据流太复杂追不动怎么办数据流分析遇到复杂逻辑时可以分段追踪。先追踪一段记录中间结果然后从中间结果继续追。不要试图一次性追到底。6.4 如何避免触发反调试被动分析本身不触发反调试但如果你使用了内存扫描工具可能会被检测到。我的建议是优先使用静态分析只在必要时才用只读扫描并且控制频率。实操心得有些游戏会检测调试器的窗口标题、进程名。如果你用IDA记得把窗口标题改掉。虽然被动分析不开调试器但养成这个习惯没坏处。7. 工具链与效率提升7.1 主力工具选择被动分析的主力工具是反汇编器。IDA Pro是行业标准功能最全但价格贵。Ghidra是免费替代品功能也很强。Binary Ninja介于两者之间界面更现代。我的建议是如果预算允许用IDA如果预算有限用Ghidra。两者都能满足大部分需求。7.2 辅助工具推荐除了反汇编器还有一些辅助工具能提升效率Strings提取程序中的字符串快速定位关键代码。PE Tools查看PE文件结构了解程序的节区布局。API Monitor观察程序调用的系统API。Process Monitor观察程序的文件、注册表、网络操作。这些工具都是只读的不会修改程序状态。7.3 脚本自动化IDA和Ghidra都支持脚本。你可以写脚本自动提取调用关系、批量重命名函数、生成分析报告。这能大幅提升效率。比如你可以写一个IDA脚本自动找出所有引用某个字符串的函数并打印它们的地址和名称。这样你就不用一个个手动查了。8. 被动分析的边界与局限被动分析不是万能的。它有几个明显的局限无法观察运行时状态你不知道某个时刻寄存器的具体值只能推断。难以处理加密和混淆如果代码被加密静态分析基本无从下手。间接调用难以完全还原虚表、函数指针、跳转表都会让调用关系变得模糊。所以被动分析通常需要和主动分析结合使用。但在游戏逆向中被动分析应该是第一步也是贯穿始终的基础。只有把被动分析做扎实了主动分析才能有的放矢。我个人在实际操作中的体会是被动分析就像拼图你手里有很多碎片但不知道全貌。你需要耐心地一块块拼每拼上一块就对整体多一分理解。这个过程很慢但很稳。当你最终把关键逻辑还原出来的时候那种成就感是主动分析给不了的。