千家信息网

如何浅析iOS手游逆向和保护

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇文章为大家展示了如何浅析iOS手游逆向和保护,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。背景介绍随着手游的发展,随之而来的手游逆向破解技术也越来越成熟,
千家信息网最后更新 2026年11月01日如何浅析iOS手游逆向和保护

本篇文章为大家展示了如何浅析iOS手游逆向和保护,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

背景介绍

随着手游的发展,随之而来的手游逆向破解技术也越来越成熟,尤其是Andorid方面,各种破解文章比比皆是,相对而言,iOS方面关于手游的逆向分析文章比较少,网易易盾移动安全专家吕鑫垚将通过分析一款unity游戏和一款cocos-lua游戏来剖析一般向的游戏破解及保护思路。

识别Unity游戏

iOS平台的ipa包可以通过压缩软件解压,一般来说Unity的游戏有如下文件目录特征:


破解思路

Unity游戏会在 \Data\Managed\Metadata下生产资源文件global-metadata.dat。游戏中使用的字符串都被保存在了一个global-metadata.dat的资源文件里,只有在动态运行时才会将这些字符串读入内存。这使得用IDA对游戏进行静态分析变得更加困难。那么为了解决这个困难,有人造了轮子,即Il2CppDumper。此可读取global-metadata.dat文件中的信息,并与可执行文件结合起来。

github:https://github.com/Perfare/Il2CppDumper 打开Il2CppDumper,会弹出一个窗口,第一个选择macho执行程序,第二个选择global-metadata.dat,然后选择对应的模式一般选auto,然后会生成如下的dump.cs 里面就是这个游戏用到的c#的接口。

有了接口以后,我们就可以搜索一般游戏修改的关键字battle,player,maxhp,fight等,然后我们定位到如果所示的类FightRoleData是我们战斗的时候角色数据来源,还有一个叫battlemanager的类,这个类是一个战斗管理者,包括开始战斗,暂停战斗,结束战斗。

public class FightRoleData : ICloneable // TypeDefIndex: 2414{    // Fields    public long Sid; // 0x10    public long OwnerId; // 0x18    public long Uid; // 0x20    public int Power; // 0x28    public int Level; // 0x2C    public int Sex; // 0x30    public int FlagType; // 0x34    public int RoleUnit; // 0x38    public int Sit; // 0x3C    public int AttackType; // 0x40    public int Race; // 0x44    public int Professional; // 0x48    public int Star; // 0x4C    public int Quality; // 0x50    public int Impression; // 0x54    public int Awaken; // 0x58    public int IsNpc; // 0x5C    public int Soul; // 0x60    public int Formation; // 0x64    public int SkinID; // 0x68    public int AwakenLv; // 0x6C    public int[][] Skills; // 0x70    public int[] Runes; // 0x78    public double Hp; // 0x80    public double MaxHp; // 0x88    public double Rage; // 0x90    public double MaxRage; // 0x98    public double Aggro; // 0xA0    public double MoveSpeed; // 0xA8    public double Attack; // 0xB0    public double PhysisDefense; // 0xB8    public double MagicDefense; // 0xC0    ...    ...    ...    // Properties    public ERolePosType PostitionType { get; }    public ERoleGender Gender { get; }    public bool IsAwaken { get; }    // Methods    public virtual void Init(ErlArray erlData); // RVA: 0x100EDDB68 Offset: 0xEDDB68    private static double _getProperty(ErlArray attrData, int index, bool[] checker, ERoleProperty property); // RVA: 0x100EDE8A0 Offset: 0xEDE8A0    public ERolePosType get_PostitionType(); // RVA: 0x100EDE93C Offset: 0xEDE93C    public ERoleGender get_Gender(); // RVA: 0x100EDE964 Offset: 0xEDE964    public bool get_IsAwaken(); // RVA: 0x100EDE97C Offset: 0xEDE97C    public object Clone(); // RVA: 0x100EDE98C Offset: 0xEDE98C    public void .ctor(); // RVA: 0x100EDE994 Offset: 0xEDE994}// Namespace: public class BattleManager : MonoBehaviour // TypeDefIndex: 3127{    // Fields    ...    ...    ...    // Properties    public Camera GameCamera { get; set; }    public GameObject CameraBase { get; }    public bool Loading { get; set; }    public BattleView battleView { get; set; }    public string BattleMusic { get; }    public Dictionary`2 RoleModelConfigDic { get; }    public int TargetFrame { get; }    public static BattleManager Instance { get; }    public DragonBallBattle Battle { get; }    public bool Pause { get; set; }    public List`1 BattleRoleControllers { get; }    public bool IsSkipSuperSkill { get; }    private bool _startAnimPlaying { get; }    // Methods    ...    ...    ...    public void StartBattle(); // RVA: 0x101BBB1EC Offset: 0x1BBB1EC    public void SkipBattle(); // RVA: 0x101BE18B0 Offset: 0x1BE18B0    ...    ...    ...}

至此,我们可以很容易实现两个功能跳过战斗,修改我们角色的攻击力,第一个功能可以通过hook StartBattle()方法然后获得this指针也就是BattleManager对象,然后我们根据BattleManager对象来调用SkipBattle()方法就可以了,第二个方式的话我们可以修改FightRoleData的数据来实现,那我们我们首先来看下FightRoleData在哪些地方被用到了,通过搜索可以发现这么个类:

// Namespace: BattleSystempublic static class BattleAPI // TypeDefIndex: 2490{    // Methods    private static T _GetConfig(long id); // RVA: 0x1000E98B4 Offset: 0xE98B4    public static DragonBallBattle Create(BattleScene scene, string hexData); // RVA: 0x100B06CFC Offset: 0xB06CFC    public static DragonBallBattle Create(BattleScene scene, byte[] dataBytes); // RVA: 0x100B0950C Offset: 0xB0950C    public static DragonBallBattle Create(BattleScene scene, BattleData data, optional CallBack`1 beforeInit); // RVA: 0x100B06E04 Offset: 0xB06E04    public static BattleRole CreateBattleRole(BattleRoleConfig roleConfig, FightRoleData roleData, BattleScene scene, DragonBallBattle battle, Dictionary`2> seqCache, optional double initialCD, optional double autoCD); // RVA: 0x100B0B3A0 Offset: 0xB0B3A0    private static int[] _getUniqueAttackSequence(int[] seq, long sid, Dictionary`2> cache, YKRandom random); // RVA: 0x100B0CC28 Offset: 0xB0CC28    private static BattleRole _createBattleRolePartner(BattlePartnerConfig partnerConfig, BattleScene scene, int[] level, DragonBallBattle battle); // RVA: 0x100B0A4B4 Offset: 0xB0A4B4    public static void ApplyProperty(BattleRoleData roleData, FightRoleData netData); // RVA: 0x100B0CDB0 Offset: 0xB0CDB0    private static BattleRole[] _getFormatBattleRoles(BattleScene scene, List`1 data, BattleFormation formatiom, int battleIndex, DragonBallBattle battle, Dictionary`2> seqCache, double[] initialCDModifier, double[] autoCD); // RVA: 0x100B09C1C Offset: 0xB09C1C    public static int ServerIndexToConfigIndex(int index, ERolePosType posType); // RVA: 0x100B0E2A8 Offset: 0xB0E2A8    public static void ImportConfig(IConfigImporter importer); // RVA: 0x100B0E390 Offset: 0xB0E390}

其中CreateBattleRole这个函数用到了FightRoleData的数据,那么我们可以通过hook CreateBattleRole这个函数,同时修改第三个参数(第一个参数是this指针)对应的roledata的偏移里面的数值比如0xB0偏移位置的attack的值达到修改攻击力的目的。

防护

Unity游戏在iOS中虽然将il转成了cpp的形式,这在一定程度上增大了逆向难度,因为转成了汇编形式不容易从代码层面去分析功能。但是因为il2cpp本身的冗余性,太多的字符串、符号信息被保留了。分析者很容易通过这些信息找到突破口,所以这里给出几点意见:

  1. 加密global-metadata.dat

  2. 在c#层面进行函数符号混淆(由于函数符号混淆容易出错所以建议对核心的几个类进行混淆)

  3. 字符串加密,代码混淆

  4. 服务端不要信任客户端,增加对数据的校验,比如我上面修改了攻击力,服务器在下发roledata的时候就需要对下发的roledata进行签名,如果我客户端修改了数据,服务器校验的时候就数据签名异常,不予以信任。

谈了点Unity游戏,现在我们来谈谈一款cocos-lua游戏。

识别Lua游戏

一般来说通过这两方面来看是不是lua脚本游戏,首先解压ipa,然后进入资源目录一般来说是src或者res,里面有类似lua,luac后缀,保险一点我们把二进制拖进ida看下:

搜索lua luajit关键字得到如图信息。

判定是lua脚本游戏。我们把lua脚本拖进游戏看下一般来说肯定是加密了,或者编译为luac/luajit形式,不然就太容易被破解了。


根据以上结果来看,不是明文存储做了加密,而且看头几个字节很有可能是采用了xxtea这种加密方式(这种方式是cocos官方提供的而且特征很明显,加密后将sign追加在文件头部作为标识。加密的key则是直接写在代码里面的)

破解思路

Lua游戏的话一般来说这么2种思路:

  1. 获取lua脚本,替换lua脚本

  2. 因为lua脚本的动态特性,我们只需要通过lua引擎去加载我们的lua脚本就能达到劫持数据的作用

我们这边通过dump的方式来获取脚本,可以通过hook luaL_loadbuffer来获取解密后的脚本,但是iOS跟安卓还是有些不同,因为安卓lua是通过so来加载的,所以必定有导出函数luaL_loadbuffer。但是iOS lua已经集成到二进制中了,所以符号自然就被strip掉了,这个时候我们可以通过字符串配合lua源码来定位,比如我这边选择的字符串是"error loading module '%s' from file",然后向上追溯就很容易找到这个函数。

对比下f5内容与luaL_loadbuffer原型

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

现在我们就开始编写代码来dump脚本,这边我用frida来实现,原因是frida对于这些一次性的需求实在是太好用了,不需要编译,不需要重启设备,开箱即用。

script = session.create_script("""var baseAddr = Module.findBaseAddress('QuickMud-mobile');var luaL_loadbuffer = baseAddr.add(0x2DF644);Interceptor.attach(luaL_loadbuffer, {    onEnter: function(args) {        var name = Memory.readUtf8String(args[3]);        var obj = {}        obj.size = args[2].toInt32()        obj.name = name;        obj.content = Memory.readCString(args[1], obj.size);        send(obj);    }} );""")def write(path, content):    print('write:', path)    folder = os.path.dirname(path)    if not os.path.exists(folder):        os.makedirs(folder)    open(path, 'w').write(content)def on_message(message, data):    if message['payload']['name']:          name = message['payload']['name']        name = "/Add/Your/Dump/Path/"+ name        content = message['payload']['content'].encode('utf-8')        dirName = os.path.dirname(name)        if not os.path.exists(dirName):            os.makedirs(os.path.dirname(name))        if name.endswith('.lua'):            write(name, content)script.on('message', on_message)script.load()sys.stdin.read()


有了解密后的脚本我们就可以通过修改脚本达到作弊的效果,因为有了源码我们甚至可以写一个脱机挂出来,这对游戏的危害极大。

防护

可以看到lua脚本如果只加密危害是很大的,所以lua游戏需要保障lua脚本的安全可以从以下几点入手:

  1. 对lua编译为luac 或者 luajit 然后在此基础上对lua引擎修改opcode,然后修改luajit的bytecode增大逆向的难度

  2. iOS虽然strip了符号,但是由于lua是开源的很容易定位到luaL_loadbuff,所以有必要加上字符串加密和代码逻辑混淆来保护游戏的安全。

上述内容就是如何浅析iOS手游逆向和保护,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

脚本 加密 字符 字符串 数据 函数 可以通过 文件 战斗 一般来说 代码 符号 分析 保护 信息 思路 方式 时候 选择 安全 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 中国互联网科技工作委员会毛军 互联网金融监管科技数据 互联网科技金融 金融科技与互联网金融 国外互联网科技网站 左右互联网络科技 科技类的互联网平台 居家互联网科技有限公司 玖二柒互联网科技公司待遇 搜狐科技互联网 如何创新互联网科技 企晟互联网信息科技有限公司 深圳艾果互联网通讯科技 无锡工业互联网科技有限公司 山东有哪些互联网科技公司 银行科技岗属于互联网吗 工业互联网科技成果转化 杭州科技互联网公司有哪些 重庆科技产业互联网有哪些 现代科技的标志之一的互联网 互联网科技公司华为 农村开互联网科技讲堂 以后互联网后是啥科技 苏州铭阳互联网科技有限公司 佛山市翊博互联网络科技有限公司 互联网科技圈年度人物 广州百诚互联网科技有限公司官网 济南小医互联网科技有限公司招聘 广州梦谷互联网科技有限公司领导 数字互联网科技有限公司
0