ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

游戏逆向被动分析实战:调用关系与数据流分析指南

游戏逆向被动分析实战:调用关系与数据流分析指南 1. 被动分析到底在分析什么很多人一听到“游戏逆向”脑子里第一反应就是打开调试器、下断点、改内存、写注入。这套打法确实有效但它属于主动分析的范畴——你一旦动手程序的状态就被你改变了反调试机制可能立刻触发游戏客户端可能直接崩溃或者把你踢下线。而被动分析走的是另一条路在完全不动一行代码、不修改任何内存、不附加调试器的前提下仅凭程序本身留下的静态痕迹把它的结构、逻辑和调用关系摸清楚。这件事听起来有点玄但实际做起来非常扎实。你可以把它类比成考古主动分析像是把文物拆开看内部结构被动分析则是通过地层、纹理、铭文去推断它当年是怎么被造出来的。游戏程序在编译之后会留下大量“化石级”的信息——函数调用关系、字符串引用、导入导出表、控制流图、数据结构布局这些都是被动分析的原材料。我之所以特别看重被动分析是因为它在实际对抗中有一个不可替代的优势零侵入。你不需要运行游戏不需要挂调试器甚至不需要联网。拿到的就是一个二进制文件安安静静地放在那里你用工具去读它它完全不知道你在读。对于带有强反调试、反注入、完整性校验的游戏来说这是最安全的切入点。这篇文章面向的是有一定逆向基础、但还没系统掌握被动分析方法的朋友。如果你之前只会用调试器硬跟或者只会用现成的修改工具那被动分析这套思路会让你的效率提升一个量级。核心关键词就四个游戏逆向、被动分析、调用关系分析、交叉引用分析、数据流分析。下面我按实际操作的顺序把这套方法拆开讲。2. 被动分析的整体思路与工具选型2.1 为什么先做被动分析再考虑主动分析我见过太多新手拿到一个游戏客户端第一件事就是附加调试器结果要么被反调试检测到直接闪退要么在茫茫多的汇编指令里迷路。被动分析的价值在于它能在你动手之前先给你一张地图。这张地图包含几个关键信息程序有哪些模块、模块之间的依赖关系是什么、哪些函数被频繁调用、哪些字符串和常量指向了核心逻辑、数据在函数之间是怎么流动的。有了这张地图你再去做主动分析就不是盲人摸象而是带着目标去验证假设。从攻防的角度看被动分析还有一个隐性好处它不会触发任何运行时检测。现在的游戏保护方案很多都是基于行为检测的——你附加调试器、你修改内存、你注入DLL这些行为都会被记录和拦截。但被动分析只读文件不产生任何运行时行为保护方案根本无从感知。2.2 工具链的选择逻辑被动分析的工具链不复杂但选型有讲究。我常用的组合是这几样工具主要用途选它的理由IDA Pro / Ghidra静态反汇编与反编译IDA的交叉引用和图形视图最成熟Ghidra免费且反编译质量不错PE-bear / CFF ExplorerPE结构解析轻量看导入表、导出表、节区信息很快Strings / FLOSS字符串提取FLOSS能自动解码混淆字符串比裸Strings强很多BinDiff / Diaphora二进制比对多版本对比时定位改动点极快Python capstone自定义分析脚本批量处理调用关系、提取特征时必备这里重点说两个选型上的考量。第一IDA和Ghidra不是二选一而是互补。IDA的交叉引用数据库和FLIRT签名识别在分析大型游戏客户端时优势明显但Ghidra的反编译器在某些编译器优化过的代码上表现更好。我的习惯是先用IDA建库、跑签名、看交叉引用遇到反编译不理想的函数再丢到Ghidra里对照。第二脚本化能力比工具本身更重要。游戏客户端动辄几十兆甚至上百兆函数数量以万计纯靠手工点根本不现实。你必须会用IDAPython或者Ghidra的脚本接口把重复性的分析工作自动化。比如批量提取所有调用某个API的函数、批量标注特定模式的代码、批量导出调用图这些用脚本几分钟就能跑完手工点可能要几天。2.3 被动分析的三个层次我把被动分析分成三个递进的层次这也是后面章节展开的顺序第一层是结构分析搞清楚程序由哪些模块组成、模块之间怎么依赖、入口点在哪里、有哪些导出函数。这一层解决的是“程序长什么样”的问题。第二层是调用关系与交叉引用分析搞清楚函数之间谁调用谁、数据在哪里被引用、关键逻辑被哪些路径触发。这一层解决的是“程序怎么运转”的问题。第三层是数据流分析追踪特定数据从输入到输出的完整路径理解加密、校验、协议构造等核心逻辑。这一层解决的是“程序在算什么”的问题。三层之间是递进关系不能跳。结构没搞清楚就去看调用关系容易迷路调用关系没理清就去做数据流基本是白费功夫。3. 结构分析先把程序的骨架摸清楚3.1 PE结构里藏着哪些关键线索拿到一个游戏客户端第一步永远是看PE结构。很多人觉得PE结构枯燥但其实里面藏着大量高价值信息。我用PE-bear打开一个典型的游戏客户端重点看这几个地方节区表Section Table是最先要看的东西。正常的节区名是.text、.data、.rdata、.rsrc这些但游戏客户端经常会有自定义节区比如.vmp0、.themida、.enigma之类这些直接告诉你用了什么保护方案。还有些节区名被故意改成乱码或者空名这本身就是一种信号——说明作者在隐藏东西。导入表Import Table能告诉你程序依赖哪些外部库。如果导入表里出现了ws2_32.dll说明有网络通信出现了d3d11.dll或opengl32.dll说明有图形渲染出现了crypt32.dll说明用了系统加密API。但要注意很多游戏会动态加载关键DLL导入表里看不到这时候就要去.rdata段里找DLL名字符串。导出表Export Table在游戏客户端里通常不多但如果有往往是核心接口。有些游戏会把关键功能做成导出函数方便自己的其他模块调用这就等于给你留了入口。资源段.rsrc经常被忽略但里面可能有配置信息、加密的脚本、甚至嵌入的DLL。我遇到过一个案例游戏的核心校验逻辑不在代码段里而是藏在资源段的一个加密二进制块里运行时才解密执行。3.2 编译器指纹与库识别结构分析的第二件事是识别编译器指纹。不同编译器生成的代码风格差异很大识别出来能帮你省很多事。MSVC编译的程序函数开头常见push ebp; mov ebp, esp或者sub esp, xxx的栈帧结构异常处理用SEH。GCC/MinGW编译的程序函数对齐方式不同常见push rbp或者直接sub rsp。Clang的风格又不一样优化后的代码更激进。识别出编译器之后下一步是库函数识别。游戏里大量使用标准库和第三方库这些库函数如果每个都手工分析纯属浪费时间。IDA的FLIRT签名和Ghidra的Function ID功能能自动识别出memcpy、strlen、malloc这些常见函数识别出来之后直接标注分析效率提升非常明显。我自己的做法是先跑一遍FLIRT签名把能识别的库函数全部标注然后剩下的未知函数才是真正需要关注的目标。一个典型的游戏客户端跑完签名之后可能有30%到50%的函数被识别为库函数剩下的才是游戏自己的逻辑。3.3 字符串与常量的情报价值字符串是被动分析里性价比最高的情报来源。一个游戏客户端里的字符串能直接告诉你很多东西错误提示信息比如“连接服务器失败”、“校验不通过”这些字符串附近的代码就是关键逻辑文件路径和注册表键指向配置存储位置URL和IP地址指向通信端点加密相关的常量比如AES的S盒、MD5的初始化向量调试信息有些游戏编译时没去掉调试字符串直接暴露函数名和文件名但字符串分析有个坑很多游戏会对字符串进行加密或混淆。裸Strings工具跑出来的可能是乱码。这时候FLOSS就派上用场了它能自动识别常见的字符串混淆模式并尝试解码。如果FLOSS也搞不定那就需要手工分析解密函数——通常字符串会在使用前被解密到栈上或者堆上你找到解密函数就能批量还原。常量的分析同样重要。比如你在.rdata段看到一串0x67452301、0xEFCDAB89、0x98BADCFE、0x10325476这是MD5的初始化常量说明附近有MD5相关逻辑。看到0x9E3779B9这是TEA加密的黄金分割常量。这些特征常量能帮你快速定位加密算法。3.4 结构分析的实操流程把上面的内容串起来我实际的结构分析流程是这样的用PE-bear打开文件记录节区表、导入表、导出表、入口点检查是否有加壳特征如果有先判断壳的类型决定是否需要脱壳用IDA加载跑FLIRT签名标注库函数用FLOSS提取字符串重点关注错误信息、路径、URL、加密常量在IDA里搜索特征常量定位加密算法查看入口点附近的代码理解程序的初始化流程导出函数列表和字符串列表建立初步的情报档案这个流程走完你对这个程序已经有了一个整体认识。接下来才是深入调用关系。4. 调用关系与交叉引用分析4.1 交叉引用是逆向的指南针交叉引用Cross Reference简称XREF是被动分析里最核心的概念。简单说就是“谁引用了这个东西”和“这个东西引用了谁”。在IDA里你选中任何一个函数、变量、字符串按X键就能看到所有引用它的位置。交叉引用分两种代码引用和数据引用。代码引用是指某个函数被哪些地方调用数据引用是指某个变量或常量被哪些指令读写。这两种引用结合起来就能勾勒出程序的逻辑网络。我举个实际例子。假设你在字符串列表里看到“校验失败”这个字符串你选中它看交叉引用发现它只被一个函数引用。你跳到那个函数看它的交叉引用发现它被三个地方调用。你再往上追发现这三个调用点分别对应登录、战斗、交易三个流程。这时候你就知道了这个校验函数是通用的三个核心流程都会走它。如果你想理解校验逻辑从这里切入就对了。4.2 调用图的构建与解读单个交叉引用是点把大量交叉引用串起来就是调用图Call Graph。IDA的图形视图能自动生成某个函数的调用关系图但那是局部的。要看全局需要用脚本导出完整的调用图。我通常用IDAPython写脚本遍历所有函数提取调用关系导出成边列表然后用Python的networkx库做图分析。分析的重点有几个入度高的函数是枢纽节点被很多地方调用通常是公共工具函数或者核心逻辑入口。出度高的函数是调度中心调用很多其他函数通常是主流程或者状态机。孤立节点要么是没被识别出来的库函数要么是死代码要么是通过间接调用比如虚函数表、函数指针被调用的。调用图还能帮你发现异常路径。正常的调用关系是有层次的如果出现循环调用或者跨层调用往往意味着有特殊逻辑。比如一个底层的加密函数直接调用了上层的UI函数这就不正常可能是回调或者钩子。4.3 虚函数表与间接调用的处理游戏客户端大量使用C虚函数表vtable是绕不开的。虚函数调用在汇编层面是间接调用静态分析工具往往追不到具体目标。这时候需要手工分析vtable的结构。vtable在内存里的布局是一串函数指针通常放在.rdata段。你找到vtable的地址看它被哪些地方引用就能找到构造函数。构造函数里会给vtable指针赋值通过分析赋值顺序能还原出类的继承关系和虚函数列表。间接调用还有一种常见形式是函数指针数组比如状态机或者消息处理表。这种结构在游戏里非常常见比如网络消息处理、技能效果处理、UI事件处理都是用函数指针表来实现的。找到这些表就等于找到了功能的索引。处理间接调用的技巧是先找表的初始化位置再找表的引用位置。初始化位置告诉你表里有哪些函数引用位置告诉你表在什么时候被使用。两者结合间接调用就变成了直接调用。4.4 调用关系分析的实操案例我拿一个实际的游戏客户端举例。这个客户端有反调试直接附加调试器会闪退所以我全程用被动分析。第一步我在字符串里搜索“debugger”找到几个相关字符串看交叉引用定位到一个函数这个函数就是反调试检测的核心。它被三个地方调用入口点、主循环、还有一个定时器回调。这说明反调试是持续检测的不是一次性的。第二步我搜索网络相关的字符串找到“packet”、“send”、“recv”这些关键词定位到网络模块。通过交叉引用我理清了网络模块的调用链主循环调用网络更新函数网络更新函数调用收包处理收包处理根据消息类型分发到不同的处理函数。第三步我分析消息处理表。这是一个函数指针数组每个元素对应一种消息类型。我导出这个数组结合字符串信息还原出了完整的消息类型列表。这时候游戏的核心通信逻辑就基本清楚了。整个过程没有运行游戏没有附加调试器纯粹靠静态分析完成。这就是被动分析的威力。5. 数据流分析追踪核心逻辑的血液5.1 数据流分析的基本方法数据流分析解决的是“数据从哪来、到哪去、中间经过了什么变换”的问题。在游戏逆向里最典型的应用场景是分析加密算法、校验逻辑、协议构造。数据流分析有两种基本方法前向分析和后向分析。前向分析从数据源头出发追踪它被哪些指令处理后向分析从数据使用点出发反推它的来源。实际分析中两种方法交替使用。举个例子你要分析一个加密函数。你先找到加密函数的输出后向分析它的来源发现输出来自一个循环循环里对输入做了异或和移位操作。你再前向分析输入发现输入来自一个缓冲区缓冲区由网络收包填充。这样加密函数的输入输出和内部逻辑就都清楚了。5.2 污点分析与传播追踪污点分析是数据流分析的高级形式。核心思想是把某个数据标记为“污点”然后追踪它在程序中的传播路径。在游戏逆向里污点分析常用来追踪用户输入、网络数据、文件读取等外部数据的流向。比如你想知道玩家的输入是怎么被处理的。你把输入数据标记为污点然后追踪它经过的每一个函数、每一次变换。最终你会发现输入数据经过了解析、校验、加密最后被发送到服务器。中间任何一个环节都是你可以切入的点。手工做污点分析很累但可以用脚本辅助。IDA的微码microcode或者Ghidra的P-code都能提供中间表示方便你做数据流追踪。我通常用IDA的微码API写脚本对特定函数做自动化的污点传播分析。5.3 加密与校验逻辑的识别游戏里的加密和校验逻辑是被动分析的重点目标。识别这些逻辑有几个常用的切入点特征常量是最直接的。AES的S盒、MD5的常量、CRC的生成多项式、Base64的字母表这些都是强特征。在IDA里搜索这些常量能快速定位加密函数。循环结构也是重要线索。加密算法通常有特征性的循环结构比如AES的轮函数、DES的Feistel结构、TEA的迭代。你在反汇编里看到固定轮数的循环配合移位和异或操作基本可以确定是加密。数据流模式能帮你区分加密类型。对称加密的输入输出长度相同哈希函数的输出长度固定非对称加密有明显的模幂运算。这些模式在数据流上表现不同识别出来能缩小范围。我遇到过一个案例游戏的通信协议用了自定义加密。我先在字符串里找到“encrypt”和“decrypt”定位到两个函数。然后分析这两个函数的数据流发现它们都调用了同一个核心变换函数这个函数里有固定的循环轮数和特征常量。我提取常量一查是XXTEA算法。整个识别过程不到半小时。5.4 数据流分析的实操要点数据流分析有几个实操上的要点都是踩坑踩出来的第一注意编译器优化。优化后的代码变量可能被复用数据流会被打乱。这时候不要死抠汇编要结合反编译器的输出看。反编译器会把优化后的代码还原成更接近源码的形式数据流更清晰。第二注意内联函数。编译器会把小函数内联到调用者里导致数据流看起来很长很乱。识别内联函数的方法是看代码模式如果一段代码在多个地方重复出现很可能就是内联函数。IDA的FLIRT签名能识别一部分剩下的需要手工判断。第三注意间接寻址。数据流经过指针或者数组时静态分析可能追不到具体位置。这时候要结合运行时信息或者用符号执行来辅助。符号执行工具比如angr能自动探索路径但性能开销大适合小范围分析。第四注意数据编码。数据在内存里可能是编码过的比如整数用了变长编码、字符串用了UTF-8或者UTF-16。分析之前要先确认编码方式否则会得出错误结论。6. 常见问题与排查技巧实录6.1 被动分析常见问题速查问题现象可能原因排查思路IDA加载后函数识别很少加壳或混淆检查节区名和入口点判断壳类型交叉引用为空间接调用或数据混淆检查vtable和函数指针表字符串全是乱码字符串加密用FLOSS或手工分析解密函数反编译结果混乱编译器优化或花指令换Ghidra对照或手工清理花指令调用图有大量孤立节点间接调用未解析分析vtable和函数指针数组数据流追踪中断间接寻址或内联结合反编译输出和符号执行6.2 加壳与混淆的应对策略加壳是被动分析最常见的障碍。判断是否加壳看几个特征入口点不在.text段、节区名异常、导入表极少、代码段熵值高。确认加壳之后有两条路脱壳或者绕过。脱壳需要分析壳的解密逻辑找到原始入口点OEPdump内存并修复导入表。这个过程本身就需要被动分析加主动调试比较复杂。如果只是想做静态分析可以考虑绕过壳——很多壳只保护代码段数据段和资源段是明文。你可以直接从数据段和资源段提取情报不一定非要脱壳。混淆的应对更考验耐心。常见的混淆手段包括控制流平坦化、虚假控制流、指令替换、字符串加密。对付混淆工具辅助很重要。IDA的插件比如D810能处理一部分控制流平坦化Ghidra的脚本能清理虚假控制流。但最终还是要靠手工理解混淆模式写针对性的清理脚本。6.3 反调试机制的静态识别被动分析虽然不触发反调试但识别反调试机制本身很重要因为它能告诉你哪些地方是敏感区域。静态识别反调试主要看几个特征调用IsDebuggerPresent、CheckRemoteDebuggerPresent等API检查PEB的BeingDebugged标志使用rdtsc指令做时间检测检查父进程名扫描调试器特征码。这些在IDA里都能通过导入表和特征指令找到。找到反调试代码之后不要急着patch。先理解它的检测逻辑和触发条件这样你在后续主动分析时才知道怎么规避。有些反调试是持续检测的有些只在启动时检测一次区别很大。6.4 我的避坑经验最后分享几个我踩过的坑不要一上来就追求完整分析。游戏客户端太大想一次性全部理清是不现实的。正确的做法是目标驱动——先明确你要解决什么问题然后只分析相关的部分。比如你要分析通信协议就只看网络模块你要分析加密就只看加密函数。其他部分暂时不管。不要忽视版本差异。游戏更新很频繁不同版本的客户端差异可能很大。分析之前先确认版本如果有多个版本用BinDiff做比对能快速定位改动点。改动点往往就是新功能或者新保护。不要只依赖一种工具。IDA和Ghidra各有优劣结合起来用效果最好。字符串分析用FLOSSPE分析用PE-bear图分析用networkx每个工具做它最擅长的事。不要忘记记录。被动分析产生的信息量很大不记录的话很快就会忘。我习惯用Markdown维护一个分析笔记记录函数地址、功能描述、调用关系、关键常量。这个笔记在后续分析中价值极高。不要忽略社区资源。很多游戏已经有前人分析过社区里可能有现成的文档、脚本、签名文件。分析之前先搜一搜能省很多时间。但要注意甄别信息的准确性不同版本的分析结果可能不通用。7. 被动分析之后的衔接被动分析做到一定程度你会面临一个选择是继续深入静态分析还是转入主动分析验证假设。我的建议是当静态分析遇到瓶颈——比如间接调用追不下去、数据流被混淆打断、关键逻辑依赖运行时状态——就应该转入主动分析。但转入主动分析之前被动分析的成果必须整理清楚。你要明确知道哪些函数是核心逻辑、哪些地址是关键数据、哪些调用路径是主要流程。带着这些信息去做主动分析效率比盲目下断点高得多。主动分析的手段包括调试器附加、内存断点、API钩子、符号执行等。这些内容超出了本文的范围但思路是一样的先有地图再动手。被动分析就是画地图的过程地图画好了后面的路就好走了。我个人在实际操作中的体会是被动分析最考验的不是工具使用能力而是耐心和逻辑推理能力。工具大家都会用但能不能从一堆交叉引用里理出逻辑链条能不能从数据流里看出算法结构这靠的是经验和思考。多分析几个样本多总结模式这种能力自然就上来了。
返回列表