尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

游戏逆向被动分析:调用关系、交叉引用与数据流分析实战

发布时间:2026/9/28 15:15:52

资讯中心
01
ARTICLE

游戏逆向被动分析:调用关系、交叉引用与数据流分析实战

游戏逆向被动分析:调用关系、交叉引用与数据流分析实战
1. 为什么“不动代码”反而是逆向分析里最被低估的能力很多人一提到游戏逆向脑子里第一反应就是打开调试器、下断点、改内存、写注入。这套主动出击的打法确实爽但真正在一线做久了你会发现被动分析才是决定你效率上限的那块地基。所谓被动分析就是在不修改目标程序、不注入任何代码、不改变运行时行为的前提下纯粹通过静态观察和只读手段去理解一个程序的结构、逻辑和数据流向。它解决的核心问题是当你面对一个完全陌生的可执行文件连它用什么引擎、模块怎么划分、关键逻辑藏在哪都不知道的时候怎么快速建立一张“地图”。这篇文章适合几类人刚接触游戏逆向、还没建立起系统分析方法的新手做了很久主动调试但总觉得“知其然不知其所以然”的中级玩家以及需要在不惊动目标的前提下做安全评估、兼容性分析、协议理解的从业者。关键词里的调用关系分析、交叉引用分析、数据流分析正是被动分析的三根支柱我会把它们拆开揉碎配上我实际踩过的坑和能直接抄的操作路径。先说一个反直觉的结论被动分析做得好的人主动调试的时间能省掉一大半。因为你在动手改之前已经知道哪个函数值得下断点、哪个数据结构是关键、哪条调用链是主逻辑。反过来上来就乱下断点的人往往在几百个断点里迷失最后靠运气碰上一个关键位置。这不是技术差距是方法论差距。被动分析的本质是把程序当成一本已经写好的书去读而不是当成一个可以随意涂改的草稿去试。读一本书你需要的是索引、目录、章节关系对应到程序里就是符号、交叉引用、调用图和数据流。下面我按实际操作的顺序一层层往下讲。2. 静态观察的第一层从文件结构里读出程序的“骨架”2.1 文件格式决定了你能看到什么拿到一个游戏可执行文件第一步不是急着反汇编而是先搞清楚它是什么格式。Windows 平台最常见的是 PE 格式安卓是 ELF主机平台各有各的容器。为什么这一步重要因为文件格式决定了哪些信息是“免费”给你的哪些需要你费劲去挖。PE 文件里导出表、导入表、节区信息、资源段、调试目录这些都是明文可读的。导入表尤其关键它直接告诉你这个程序依赖了哪些外部库——是 DirectX 还是 OpenGL是 Lua 还是 Python 解释器是自研网络库还是 libcurl。我见过太多人跳过这一步直接进反汇编结果在几万行汇编里找一个明明导入表里就写着的函数。用工具看一眼导入表你就能大致判断出这个游戏的技术栈。比如看到lua_pcall、luaL_loadbuffer这类符号基本可以确定逻辑层跑在 Lua 上看到il2cpp_开头的符号那就是 Unity 的 IL2CPP 后端看到mono_开头是 Unity 的 Mono 后端。这个判断能帮你省掉大量盲目搜索的时间。2.2 节区布局透露的编译与保护信息节区Section的命名和属性也是一座信息富矿。标准的.text是可执行代码.data是已初始化数据.rdata是只读数据.bss是未初始化数据。但实际游戏里你经常看到一些非标准节区名比如.vmp0、.themida、.enigma这些基本就是壳或虚拟化保护的标志。这里有个经验不要一看到壳就放弃被动分析。很多壳只保护了部分节区或者只在启动阶段解密、运行到某个点之后代码段就是明文了。你可以先观察节区的熵值熵接近 8 的节区大概率是加密或压缩的熵在 6 左右的往往是正常编译产物。用只读的方式 dump 内存镜像再分析依然属于被动分析的范畴因为你没有改变程序的执行逻辑。还有一个细节是节区的对齐和大小。如果某个节区的虚拟大小远大于物理大小说明里面有大量运行时才填充的数据这通常指向动态生成的代码或解密后的缓冲区。这些观察都不需要你运行程序纯静态就能完成。2.3 时间戳、编译器和字符串的交叉验证PE 头里的时间戳、链接器版本、Rich Header 这些元数据能帮你判断这个程序大概是什么年代、用什么工具链编译的。这不是为了考古而是为了缩小你选择分析工具和脚本的范围。比如一个 2010 年前后用 VC 编译的程序和 2023 年用 Clang 编译的程序它们的函数序言、调用约定、异常处理结构都不一样你写 IDA 脚本时的匹配规则也得跟着变。字符串是最容易被忽视但性价比极高的信息源。直接对二进制做 strings 提取你能看到报错信息、配置键名、URL 路径、甚至残留的调试输出。我印象很深的一次是在一个游戏的字符串里发现了完整的 Lua 脚本路径命名规则顺着这个规则直接在资源包里定位到了核心逻辑脚本整个过程没开过一次调试器。字符串不会告诉你逻辑怎么走但它会告诉你“这里有什么”这是建立地图的第一步。3. 交叉引用分析把孤立的函数串成一张关系网3.1 交叉引用的本质是“谁用了谁”交叉引用Cross Reference简称 XREF是被动分析里最核心的概念。它的逻辑非常朴素任何一个函数、变量、字符串只要被别的地方用到了就存在一条引用关系。反汇编器会自动帮你把这些关系标出来你要做的是读懂它们。在 IDA 或 Ghidra 里你选中一个函数按 X 键就能看到所有调用它的位置。这个动作看起来简单但它解决的是一个根本问题从任意一个点出发你都能顺着引用关系走到程序的任意角落。这就像在一个陌生城市里只要你知道一个地标就能通过路牌找到其他所有地方。我通常的切入方式是“从字符串反推”。先找到一条有意义的字符串比如“Login failed”或者“Inventory full”看它的 XREF找到引用它的函数那个函数大概率就是处理登录或背包逻辑的地方。然后再看这个函数的 XREF往上找调用者往下找被调用者一层层展开一张逻辑地图就出来了。3.2 调用图与调用树的区别以及什么时候用哪个很多人把调用图和调用树混为一谈其实它们回答的是不同的问题。调用图Call Graph是全局视角展示所有函数之间的调用关系适合用来理解整体架构调用树Call Tree是局部视角从某个根函数出发展示它直接和间接调用的所有函数适合用来深挖某条具体逻辑。我的习惯是先用调用图建立宏观认知找到几个“枢纽函数”——那些被大量其他函数调用的节点。枢纽函数往往是核心管理器、事件分发器或者主循环。锁定它们之后再对每个枢纽函数生成调用树逐层往下看。这样你既不会迷失在全局的复杂度里也不会陷入某个局部细节出不来。这里有个坑要提醒递归和间接调用会让调用图变得极其复杂。游戏里常见的虚函数调用、函数指针回调、事件系统在静态调用图里往往显示为“无法确定目标”。这时候不要硬啃标记下来留到后面用数据流分析或者动态验证去补。被动分析不是要求你一次看透所有东西而是要求你清楚地知道“哪些已经确定哪些还是未知”。3.3 从引用密度判断代码的重要性一个实用的技巧是看引用密度。被引用次数特别多的函数要么是工具函数要么是核心逻辑。工具函数通常很短逻辑简单看一眼就能排除核心逻辑则往往结构复杂值得深挖。你可以按引用次数排序从高到低扫一遍快速过滤掉那些明显的工具函数比如内存拷贝、字符串处理剩下的就是重点。反过来引用次数极少甚至为零的函数往往是回调函数、虚函数实现或者导出给外部调用的接口。零引用的函数不代表不重要它可能正是某个事件触发时才被调用的关键逻辑。这类函数需要结合数据流分析看它的参数从哪里来、返回值到哪里去。4. 数据流分析理解“值”是怎么在程序里流动的4.1 数据流分析要回答的三个问题如果说交叉引用分析解决的是“谁调用谁”那数据流分析解决的就是“值怎么变”。具体来说它要回答三个问题这个值从哪里来、中间经过了哪些变换、最终到哪里去。在游戏逆向里这三个问题对应的是输入怎么进入程序、逻辑怎么处理输入、结果怎么影响游戏状态。举个具体的例子。假设你在分析一个伤害计算逻辑你找到了一个疑似计算伤害的函数。光看这个函数本身你只能看到它做了加减乘除但你看不到它的输入是从哪里来的。通过数据流分析你往上追溯参数来源可能会发现它来自一个结构体而这个结构体是在另一个函数里根据角色属性填充的。再往上追角色属性又来自配置表或网络包。这样一条完整的链路才是真正理解了伤害计算。4.2 寄存器与栈的追踪方法在汇编层面做数据流分析核心是追踪寄存器和栈槽的变化。x86-64 下函数参数通常走rdi、rsi、rdx、rcx、r8、r9返回值在rax。你要做的是在反汇编视图里跟着这些寄存器的读写走看它们在哪里被赋值、在哪里被使用。这个过程手动做很累但工具能帮大忙。IDA 的反编译视图Hex-Rays会把汇编还原成接近 C 的伪代码变量之间的赋值关系一目了然。Ghidra 的反编译器也有类似能力。我的建议是先用反编译视图建立整体理解再回到汇编去验证关键细节。反编译视图可能有不准确的地方尤其是涉及优化和特殊指令时但作为理解数据流的起点它效率极高。栈槽的追踪稍微麻烦一点因为栈偏移在不同函数里是相对的。你需要关注的是rbp或rsp的相对偏移以及局部变量在栈上的布局。一个常见的模式是函数开头sub rsp, XXX分配栈空间然后各种mov [rspoffset], reg往栈上写值。这些栈槽就是局部变量追踪它们的读写就能还原局部变量的生命周期。4.3 结构体识别数据流分析的终极目标数据流分析做到深处你会发现很多值不是孤立的而是成组出现的。比如一个角色对象它的血量、魔法、坐标、朝向往往在内存里是连续或按固定偏移排列的。识别出这些结构体是从“看懂单个函数”跃升到“看懂整个系统”的关键。识别结构体的方法有好几种。最直接的是看内存访问模式如果多个函数都以某个基址加上不同偏移的方式访问内存那这个基址很可能就是一个结构体指针不同偏移就是不同字段。另一种方法是从字符串或常量反推如果某个偏移处总是出现特定的魔法数字或字符串指针那这个字段的用途就基本确定了。我习惯在 IDA 里手动创建结构体把观察到的偏移和类型填进去然后应用到所有相关函数。这样反编译视图会立刻变得清晰很多原本的*(_DWORD *)(a1 0x18)会变成character-health这样的可读形式。这个投入非常值得尤其是在分析大型游戏时结构体识别能把你后续所有分析工作的效率提升一个档次。5. 把三种技术串起来一次完整的被动分析实战推演5.1 从入口点出发的宏观扫描假设我们拿到一个陌生的游戏客户端没有任何符号没有任何文档。第一步是找到入口点。PE 文件的入口点在 Optional Header 的 AddressOfEntryPoint 字段工具会自动定位。但游戏的实际逻辑入口往往不是这个而是引擎初始化之后的某个回调。我的做法是先看入口点附近的代码找到main或WinMain的调用然后顺着初始化流程往下走。初始化流程里通常会有引擎创建、资源加载、脚本系统启动这些步骤。每一步都会调用一系列函数这些函数的交叉引用会自然地把我们引向核心模块。这个阶段不要陷太深目标是建立一张粗略的地图哪些模块存在、它们大概负责什么、模块之间的边界在哪里。用调用图工具生成一张全局图把明显的聚类标记出来比如渲染相关的一堆函数、网络相关的一堆函数、UI 相关的一堆函数。聚类内部的函数引用密度高聚类之间的引用密度低这个特征能帮你快速划分模块。5.2 锁定关键逻辑的“三跳原则”当你需要找某个具体逻辑时比如“背包物品使用”我总结了一个“三跳原则”从字符串跳函数从函数跳调用链从调用链跳数据结构。第一跳用字符串交叉引用找到最直接的函数第二跳用调用图找到这个函数的上下文第三跳用数据流分析找到它操作的数据结构。这个原则之所以有效是因为游戏的逻辑层通常有清晰的命名或提示性字符串。即使字符串被加密或混淆UI 层的文本、配置文件的键名、网络协议的字段名也往往留有线索。三跳之内如果还找不到说明你的切入点选错了换一个字符串或换一个已知函数重新开始比在死胡同里硬钻要高效得多。5.3 被动分析的边界什么时候必须转主动被动分析很强但它有边界。当逻辑依赖运行时才确定的值、当代码被虚拟化保护、当关键数据来自网络且经过加密时纯静态分析会卡住。这时候你需要判断是继续用更高级的静态技术比如符号执行、污点分析还是转入主动调试。我的经验是如果一个函数在静态视图里看起来“逻辑完整但输入不明”那大概率需要动态验证输入。如果一个函数根本看不到有效指令比如被虚拟化那静态分析的成本会极高不如直接上调试器观察行为。被动分析和主动分析不是对立的而是接力关系。被动分析负责建立地图和假设主动分析负责验证假设和填补空白。6. 工具链的选择与那些没人告诉你的实操细节6.1 IDA、Ghidra、Binary Ninja 的取舍这三款是主流选择各有脾气。IDA 的反编译最强生态最成熟但价格高对新手不友好Ghidra 免费开源反编译质量这几年进步很大脚本能力强但界面和性能偶尔让人抓狂Binary Ninja 介于两者之间API 设计现代适合写自动化分析工具。我的实际搭配是Ghidra 做初筛和批量分析IDA 做深度手工分析。Ghidra 的批量脚本能力可以快速处理大量函数生成初步的调用图和交叉引用报告IDA 则在需要精细阅读和手动标注时无可替代。如果你只能选一个新手我建议从 Ghidra 开始成本低而且它的反编译器足够你理解大部分逻辑。6.2 符号恢复的实用技巧没有符号的程序就像没有路名的城市。符号恢复是被动分析里回报最高的投入之一。除了前面说的结构体识别函数命名也很关键。我的命名习惯是用“模块_动作_对象”的格式比如Inventory_UseItem、Network_SendPacket、Combat_CalcDamage。这样即使过了几个月再回来看也能快速回忆起每个函数的作用。对于虚函数和回调命名要标注来源比如Callback_OnLoginResponse、VFunc_Character_Update。对于不确定的函数用sub_XXXX加注释说明你的猜测不要强行起一个可能误导的名字。符号恢复不是一次性的工作而是随着理解加深不断迭代的过程。6.3 那些让我踩过坑的细节第一个坑是过度依赖反编译视图。反编译器在遇到优化代码、内联汇编、异常处理时经常出错如果你完全信任它可能会得出错误结论。我的做法是反编译视图用来理解意图关键逻辑一定回汇编验证。第二个坑是忽视编译器优化带来的假象。比如编译器可能把两个独立的变量合并到一个寄存器里或者把循环展开成直线代码。这些优化会让静态分析看到的代码和源码结构差异很大。遇到看起来“奇怪”的代码模式时先想想编译器可能做了什么优化而不是急着下结论说这里有混淆。第三个坑是在字符串上花太多时间。字符串是很好的起点但不是终点。有些游戏的字符串被加密或运行时才解密静态提取出来的是乱码。这时候不要死磕字符串换用导入表、节区特征、代码模式匹配等其他切入点。7. 被动分析在游戏逆向攻防中的真实定位7.1 防守方视角被动分析是理解保护效果的前提如果你站在防守方想评估自己的保护方案是否有效被动分析是必须掌握的手段。因为攻击者第一步做的就是被动分析你得知道他们在静态视图里能看到什么、看不到什么。如果你的关键逻辑在静态视图里一目了然那保护就是失败的如果攻击者需要大量动态调试才能理解那保护至少起到了拖延作用。一个实用的自检方法是用被动分析工具完整过一遍自己的程序记录下哪些信息是“免费暴露”的。函数名、字符串、导入表、结构体布局这些都是攻击者的起点。你能做的是减少这些免费信息增加他们建立地图的成本。7.2 进攻方视角被动分析决定了攻击的效率站在分析者角度被动分析的质量直接决定了后续所有工作的效率。我见过太多人跳过被动分析直接上调试器结果在几千个断点里反复试错几天下来连主循环都没找到。而被动分析做扎实的人往往半天就能画出核心逻辑的调用图然后有针对性地在关键函数上下断点一两个断点就能定位问题。被动分析不是慢它是把慢功夫花在前面换来后面的快。这个账要算清楚。你花在静态分析上的每一个小时都会在动态调试阶段以数倍的效率回报回来。7.3 两种分析的切换时机判断最后一个实操问题什么时候该从被动转主动我的判断标准是三条第一静态视图里出现了无法确定的目标比如间接调用、虚函数分发第二关键数据的来源在静态视图里断了第三你需要验证一个静态分析得出的假设。满足任意一条就可以考虑上调试器了。但即使转了主动被动分析的成果依然在发挥作用。你之前建立的调用图、识别的结构体、恢复的符号都会让动态调试的每一步都更有方向。被动分析和主动分析不是两个阶段而是一个循环静态建立假设动态验证假设验证结果又反过来丰富静态理解。这个循环转得越快你理解程序的速度就越快。我在实际项目里最深的体会是被动分析的能力差距本质上是对“程序结构”敏感度的差距。同样一个二进制有人看到的是几万个孤立函数有人看到的是清晰的模块划分和数据流。这个敏感度不是天生的是靠一次次从字符串追到函数、从函数追到调用链、从调用链追到结构体的训练积累出来的。你每完整走通一次这样的链路下一次就会更快。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。