尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity手游逆向实战:定位与修改il2cpp碰撞检测逻辑

Unity手游逆向实战:定位与修改il2cpp碰撞检测逻辑 1. 项目概述为什么我们要动Unity手游的碰撞逻辑做手游逆向的特别是Unity游戏绕不开的一个核心就是修改游戏的核心玩法逻辑。而碰撞逻辑可以说是几乎所有动作、射击、RPG甚至休闲游戏的心脏。为什么这么说想想看你的角色能不能穿过一堵墙你的子弹能不能击中敌人你捡起一个道具时会不会穿模甚至你掉下悬崖会不会摔死本质上都是游戏里一个个“碰撞体”在对话。对于开发者这是游戏规则对于我们这些搞逆向的这就是最诱人的“后门”。这次要聊的就是一个非常具体且硬核的实战针对采用Unity3D il2cpp技术栈打包的安卓手游从零开始定位并修改其碰撞检测逻辑。il2cpp是Unity将C#脚本代码编译成C再进一步编译为原生机器码如ARM汇编的中间件。它带来的好处是性能大幅提升代码更难被直接反编译回可读的C#源码安全性增强。但这并不意味着无懈可击它只是把战场从.NET的中间语言IL转移到了更底层的原生二进制世界。我们的武器库也很明确IDA Pro作为静态反汇编和动态调试的神器以及il2CppDumper这个专门为il2cpp游戏“解包”的桥梁工具。这个项目的价值在哪对于安全研究人员这是理解游戏反外挂机制、挖掘漏洞的必经之路对于独立开发者这是学习成熟商业游戏设计思路的绝佳途径对于Mod爱好者这是创造全新游戏体验的开始。整个过程就像是在一片由0和1构成的混沌森林里根据蛛丝马迹绘制出一张通往宝藏的精确地图并亲手改写地图上的规则。2. 核心工具链与逆向环境搭建工欲善其事必先利其器。在开始“手术”前我们必须准备好无菌的手术室和全套手术刀。整个流程的核心工具链可以概括为解包 - 提取与恢复符号 - 静态分析 - 动态验证 - 修改与重打包。2.1 基础工具准备首先你需要一个目标APK文件。这里我们以一个虚构的横版动作游戏DemoFighter.apk为例。以下是所需工具的清单及其作用APK解包工具如apktool用于解压APK获取其中的assets、lib原生库、AndroidManifest.xml等资源文件。我们最关心的是assets/bin/Data/Managed/Metadata/global-metadata.dat文件这是il2cpp的“地图钥匙”。java -jar apktool.jar d Demofighter.apk -o output_folderil2CppDumper核心中的核心。它利用global-metadata.dat和对应的libil2cpp.so通常在lib/arm64-v8a等架构目录下尝试恢复出函数名、方法签名、类结构等符号信息生成一个IDA Pro可以识别的脚本文件。这相当于把一堆匿名汇编代码贴上了写有“Player::Update”、“CollisionSystem::Check”等标签。注意il2CppDumper有多个版本如GUI版、命令行版务必选择与游戏Unity版本匹配的发布版。通常新版Unity需要更新的Dumper。IDA Pro建议7.7以上版本逆向工程的瑞士军刀。我们将用它加载libil2cpp.so并运行il2CppDumper生成的脚本让冰冷的汇编代码呈现出清晰的结构。IDA的静态分析和动态调试Debugger功能是我们分析逻辑和验证修改点的关键。十六进制编辑器如 HxD, 010 Editor用于直接修改libil2cpp.so或global-metadata.dat的二进制数据。这是最终实施“手术”的地方。签名工具如apksigner,uber-apk-signer修改后的APK必须重新签名才能在安卓设备上安装。2.2 关键步骤使用il2CppDumper恢复符号这一步是成败的关键。假设我们已经用apktool解包在output_folder/lib/arm64-v8a/下找到了libil2cpp.so在output_folder/assets/bin/Data/Managed/Metadata/下找到了global-metadata.dat。运行il2CppDumper以GUI版为例选择libil2cpp.so文件。选择global-metadata.dat文件。选择正确的Unity版本如果自动识别失败需要手动尝试相近版本。点击“Dump”。成功后会在同目录生成一系列文件其中最重要的两个是dump.cs一个尝试重建的C#伪代码文件虽然无法直接编译但提供了极其宝贵的类名、方法名和大致逻辑结构参考。script.py一个IDA Python脚本用于将符号信息导入IDA Pro。2.3 将符号导入IDA Pro用IDA Pro打开libil2cpp.so。初次分析会花费较长时间请耐心等待。等待初始自动分析结束后点击File - Script file...选择刚才生成的script.py脚本并运行。脚本运行过程中IDA的输出窗口会滚动大量信息。运行完毕后你会发现IDA左侧的“Functions Window”里大量原本是sub_XXXXXX的匿名函数变成了像PlayerController__Update、Collider__get_enabled这样可读的名字。同时IDA的“Structures”窗口也会自动创建并填充好il2cpp的运行时结构体如String、Array等以及游戏自定义的类结构。实操心得恢复符号的成功率并非100%尤其对于使用了代码混淆或自定义元数据处理的游戏。如果导入后函数名仍然大量缺失可以尝试以下方法① 检查Unity版本是否选对② 尝试il2CppDumper的不同模式如“Auto”、“Plus”③ 在社区寻找针对该游戏特定版本的Dump信息。符号恢复得越完整后续分析难度呈指数级下降。3. 静态分析在IDA中定位碰撞逻辑现在我们面对的是一个被贴上了大量标签的庞大二进制文件。如何从中找到“碰撞逻辑”这根针这需要结合对Unity引擎的了解和合理的搜索策略。3.1 理解Unity的碰撞检测体系Unity的碰撞检测主要依赖于Collider碰撞体组件和物理引擎。核心逻辑通常围绕以下几个关键点OnCollisionEnter、OnTriggerEnter等方法这是C#脚本中响应碰撞的入口。在il2cpp中这些方法会被编译成独立的函数。Physics.Raycast、OverlapSphere等查询函数用于主动进行碰撞检测。Rigidbody、CharacterController这些组件内部包含了复杂的碰撞解析逻辑。游戏自定义的碰撞处理类例如DamageCalculator、ObstacleManager等。我们的目标就是找到调用这些引擎函数或包含碰撞处理逻辑的游戏自有函数。3.2 搜索与追踪策略字符串搜索在IDA中按ShiftF12打开字符串窗口搜索关键词如 “Collision”, “Trigger”, “Damage”, “Hit”, “Raycast”, “Overlap”。找到相关字符串后双击跳转到引用该字符串的代码位置向上回溯函数调用链。函数名搜索利用恢复的符号在函数窗口View - Open subviews - Functions中搜索包含上述关键词的函数名。例如可能会找到Player__OnCollisionEnter、Bullet__OnTriggerHit。交叉引用分析XREFs这是最强大的静态分析手段。假设我们通过字符串找到了一个“Player Damaged!”的提示。定位到这个字符串后查看谁引用了它在数据上按X可能就能找到ApplyDamage函数。再查看谁调用了ApplyDamage可能就能找到ProcessCollision函数。分析已知的引擎函数il2CppDumper恢复的符号里包含Unity引擎自身的函数如Physics_Internal_RaycastTest。找到这些引擎函数的调用点分析其周围的游戏逻辑是定位碰撞代码的捷径。3.3 案例定位一个“无敌”碰撞逻辑假设我们的目标是让角色碰撞到敌人时不受伤害。一个典型的逻辑链可能是OnCollisionEnter(检测到碰撞) -GetColliderTag(判断碰撞对象) -IsEnemy(确认是敌人) -PlayerHealth.Reduce(减少生命值)。在IDA中的操作搜索字符串 “Health” 或 “Damage”找到可能显示伤害数值或死亡提示的字符串。追踪到显示该字符串的函数例如UIManager__ShowDamageText。查看该函数的交叉引用找到调用它的函数例如PlayerAvatar__TakeDamage。分析TakeDamage函数它很可能有一个条件判断比如检查角色是否处于“无敌状态”isInvincible true或者是否触发了格挡。这个判断的汇编代码通常表现为一个条件跳转指令如BNE,BEQ,CBZ。我们的修改目标就是让这个条件跳转永远指向“不扣血”的分支。例如原本是if (isInvincible) return;我们可以通过修改让函数在判断前就直接return或者强制将isInvincible设置为true。注意事项静态分析时要特别注意函数的调用约定和参数传递。ARM64架构下前8个参数通常通过寄存器X0-X7传递。在分析类似TakeDamage(int amount)这样的函数时伤害值amount很可能就在X1或W1寄存器中。理解这些约定对后续的动态调试和修改至关重要。4. 动态调试与逻辑验证静态分析给了我们一张地图但地图是否正确路上有没有陷阱需要动态调试来验证。我们将使用IDA Pro的远程调试功能连接真机或模拟器中的游戏进程。4.1 配置IDA远程调试环境准备调试环境在安卓设备上安装调试版的游戏APK即我们解包后未修改的APK用调试密钥签名。确保设备已开启USB调试并通过ADB连接电脑。推送调试服务将IDA安装目录下的android_server或对应架构的android_server64推送到设备并运行。adb push android_server64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/android_server64 adb shell /data/local/tmp/android_server64端口转发新开一个命令行窗口进行端口转发。adb forward tcp:23946 tcp:23946IDA附加进程在IDA中选择Debugger - Select debugger - Remote ARM Linux/Android debugger。在调试器设置中Hostname填localhostPort填23946。然后点击Debugger - Attach to process...在列表中找到游戏进程并附加。4.2 下断点与跟踪执行流附加成功后游戏会暂停。我们回到静态分析时找到的疑似碰撞处理函数例如PlayerAvatar__TakeDamage。在函数的入口地址按F2下断点。点击F9继续运行游戏。然后操作游戏让角色发生一次应受伤害的碰撞。如果我们的分析正确游戏会立刻在断点处暂停。此时我们可以查看寄存器确认X0是否是this指针指向PlayerAvatar对象X1是否是我们预期的伤害值。查看栈内存分析传入的其他参数。单步执行F7/F8一步步跟踪程序的执行流观察它如何判断、如何跳转。重点关注那些决定是否扣血的CMP比较和B.cond条件分支指令。修改寄存器实时测试在断点暂停时我们可以直接修改寄存器的值。例如在判断isInvincible的标志时如果对应的寄存器是W19且CBNZ W19, loc_XXXXX表示如果W19非零真就跳转到不扣血的分支。我们可以在执行这条指令前在IDA的寄存器窗口中手动将W19的值改为1然后继续执行观察角色是否免伤。这是验证逻辑最直接的方法。实操心得动态调试手游可能非常不稳定游戏可能有反调试检测。如果一附加进程游戏就闪退可能触发了反调试。可以尝试在调试器设置中隐藏调试痕迹如Debugger - Debugger options - Set specific options中的Suspend on library load/unload取消勾选或者寻找绕过反调试的方法这本身就是一个深水区。对于新手先从没有强保护的单机小游戏练手。5. 二进制修改与重打包经过动态调试我们确信找到了修改点。现在是动真格的时候了——直接修改libil2cpp.so的二进制代码。5.1 修改策略与汇编指令常见的修改有以下几种我们以ARM64汇编为例强制跳转NOP流或B指令让函数在关键判断前直接返回或者强制跳转到我们想要的分支。目标让TakeDamage函数无效。方法在函数最开头写入返回指令。对于返回void的函数通常是RET。对应的机器码是C0 03 5F D6对于ARM64。我们可以用十六进制编辑器找到函数开头的地址将前4个字节替换为C0 03 5F D6。更优雅的方法如果函数开头有其他必要的初始化不能跳过我们可以修改那个关键的条件跳转。例如将BNE loc_xxxx不相等则跳转改为B loc_xxxx无条件跳转或者将其NOP掉全部填1F 20 03 D5让程序顺序执行到扣血逻辑的相反分支。修改立即数例如将伤害值固定为0或1。目标将TakeDamage(100)的调用改为TakeDamage(0)。方法找到传递伤害值100的指令。它可能是一条MOV指令如MOV W1, #0x64#0x64即十进制的100。我们可以将其改为MOV W1, #0x0。需要计算新指令的机器码。修改布尔值将false改为true。目标让isInvincible恒为真。方法找到加载这个布尔值的指令。它可能从内存加载到一个寄存器如LDRB W0, [X19, #0x10]然后参与比较。我们可以修改内存中的值复杂或者更简单地在比较指令后强制让代表“真”的条件成立。例如在CMP W0, #0之后将B.EQ等于0则跳转即false跳转改为B.NE不等于0则跳转或者直接NOP掉这条B.EQ让它“掉”进真分支。5.2 使用IDA生成补丁手动计算机器码容易出错。IDA Pro提供了更安全的方式Edit - Patch program - Assemble。在需要修改的指令地址处打开汇编对话框。直接输入你想要的汇编指令例如输入RET。IDA会显示对应的机器码并询问是否应用补丁。应用后修改仅存在于IDA的数据库中。最后使用Edit - Patch program - Apply patches to input file...将修改写回到原始的libil2cpp.so文件。5.3 重打包与签名将修改后的libil2cpp.so文件替换回apktool解包输出目录的对应位置如output_folder/lib/arm64-v8a/。使用apktool重新打包APK。java -jar apktool.jar b output_folder -o modified_DemoFighter.apk对新的APK进行签名。使用Java的apksigner需要JDK或更方便的uber-apk-signer。# 使用apksigner示例 (需要debug.keystore) apksigner sign --ks debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android --out signed_modified_DemoFighter.apk modified_DemoFighter.apk将签名后的APK安装到设备上进行测试。如果游戏能正常启动并且碰撞逻辑已按预期修改例如角色无敌则大功告成。6. 实战中常见问题与深度排查技巧即使按照流程走也一定会遇到各种问题。这里记录一些典型的“坑”和解决思路。6.1 il2CppDumper失败或恢复符号不全现象运行Dumper时报错或生成的dump.cs文件内容空洞IDA导入脚本后函数名依旧稀少。排查版本匹配这是最常见的原因。确认游戏的Unity版本有时在assets/bin/Data/globalgamemanagers文件中用字符串搜索“Unity”可以找到并使用对应版本或更新版本的il2CppDumper。文件匹配确保global-metadata.dat和libil2cpp.so来自同一个APK的同一版本没有混用。加密/混淆部分游戏会对这两个文件进行自定义加密或压缩。需要先进行解密。观察文件头是否异常或尝试在社区搜索该游戏特定的解包工具。尝试不同模式il2CppDumper的“Manual”模式允许你手动指定一些偏移量有时能破解简单的混淆。6.2 动态调试时断点无法命中或游戏崩溃现象下了断点触发碰撞后游戏不暂停或者一暂停游戏就崩溃。排查地址偏移ASLR动态链接库加载的基地址每次运行时都可能不同地址空间布局随机化。IDA中下断点的地址是静态文件中的偏移地址File Offset。调试时需要加上模块的加载基址Module Base。在IDA调试视图中通过CtrlS查看模块列表找到libil2cpp.so的实际加载地址。实际断点地址 加载基址 文件偏移。更简单的方法是在静态视图的函数入口下断点IDA在附加调试时会自动为你计算好实际地址前提是符号加载正确。反调试检测游戏可能通过检查TracerPid、ptrace、特定端口等方式检测调试器。症状是附加后立刻闪退。解决方案包括使用调试器隐藏插件、修改内核参数需要root、或者寻找游戏内关闭反调试的“后门”例如某些单机游戏在开发模式留有开关。断点类型确保在代码段.text下软件断点F2不要在数据段或不可执行区域下断点。6.3 修改后游戏闪退或逻辑异常现象修改了libil2cpp.so并重打包后游戏启动崩溃或进入游戏后角色行为异常如无法移动、UI错乱。排查指令修改错误这是最可能的原因。ARM指令是定长或对齐的通常是4字节。如果你把一条4字节指令替换成了2字节的机器码会导致后续所有指令错位必然崩溃。务必使用IDA的汇编功能进行修改避免手动计算错误。破坏了函数指针或跳转表有些地址被其他代码以指针形式引用。如果你修改了函数头但其他地方仍通过指针调用原函数可能导致调用到错误地址。修改时尽量选择函数内部的、局部化的跳转点避免动函数入口。签名或打包问题确保重打包过程没有损坏其他资源文件。可以尝试只修改so文件其他都不变用原签名方式签名来排除打包工具的问题。逻辑副作用你的修改可能产生了意想不到的副作用。例如你让TakeDamage函数直接返回但它内部可能还负责播放受击音效、触发受击动画、设置受击状态等。这些副作用被跳过可能导致角色状态机混乱。更精细的修改是只跳过扣血逻辑但保留其他效果。这需要更深入的分析找到扣血逻辑和其他逻辑的分支点。6.4 进阶处理代码混淆与保护一些中重度游戏会使用商业保护方案如腾讯加固、网易易盾等或自定义混淆。整体加固整个DEX或SO被加壳需要先脱壳才能看到真实代码。这涉及到动态脱壳技术门槛较高。控制流扁平化将正常的if-else、switch逻辑打乱用大量的跳转和状态变量来实现使静态分析极其困难。对付这种动态调试跟踪是唯一可靠的方法。字符串加密所有硬编码字符串都加密存储运行时解密。这使得字符串搜索失效。你需要找到解密函数或者通过动态调试时内存中的明文字符串来定位关键代码。虚拟机保护VMP将关键代码转换为自定义字节码在自定义虚拟机中执行。这是最高级别的保护之一逆向难度极大通常需要深入分析其虚拟机解释器。对于新手我的建议是远离有强保护的热门网游从单机、弱保护或旧版本的游戏开始练习。逆向工程是一个需要极大耐心和细心的领域每一个成功的修改背后都是无数次静态分析、动态跟踪和试错的结果。从简单的“无敌”、“一击必杀”功能修改做起逐步理解游戏代码的组织结构、引擎API的调用方式积累对汇编代码的“感觉”这才是从零到一的正确路径。当你第一次成功让游戏角色按照你的意志穿越墙壁时那种亲手掌控数字世界的成就感是无与伦比的。
返回列表