要我帮设计推荐信息摘要:

小游戏 Brain Test 3 大脑测试3 跳过付费内容 无限内购游戏属于il2cpp,现在很多的安卓游戏都采用了这个打包。对于这类游戏,常见的改法是修改dex的入口,在入口处添加一个调用代

小游戏 Brain Test 3 大脑测试3 跳过付费内容 无限内购
游戏属于il2cpp,现在很多的安卓游戏都采用了这个打包。对于这类游戏,常见的改法是修改dex的入口,在入口处添加一个调用代码,每次启动都自动修改sp的long值,这样就打到了无限金币的目的。还有另一种改法就是直接修改so文件,通过修改底层代码,可以实现的玩法也比较多,比如不减反增、定死数值、免费购物、全部解锁等。但是相对的也比较麻烦,对于没有汇编经验的萌新来说,还是写个Java实在。

软件样品为当前最新版1.50.0。

准备工具
1、IDA32
2、Il2CppDumper


想不到这游戏已经出到第三部了,前两部我还发过帖子如何修改无限提示,不过是改dex的。现在换另一种方法,改so破解游戏。
游戏风格比较适合小朋友,有些关卡我实在想不通为什么是这样……
游戏有广告,怎么去掉我就不说了,用幸运破解器一键搞定就好了,来来去去都是那几个熟面孔。


先看一下游戏界面,第一次玩游戏的时候会送50个电灯泡,正常情况下会越用越少,那么就会有相减的逻辑,修改so的时候可以利用相反的指令让灯泡越来越多,也就是不减反增。

下好Il2CppDumper后,顺序打开相应的文件,libil2cpp、global-metadata.dat。等待分析完成后打开dump.cs文件备用。
接着ida打开so文件,选择左上角运行如下两个脚本文件,静静等待加载完毕即可。电脑差的会比较久,可以先去喝杯奶茶再回来看看


之前说过这类游戏的数值一般储存在sp里面,位于com.unicostudio.braintest3.v2.playerprefs.xml,打开后他是长这样的:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <int name="cross_promo_id" value="61" />
    <string name="unity.player_session_count">5</string>
    <int name="Screenmanager%20Resolution%20Height" value="2220" />
    <string name="unity.player_sessionid">4976473052650692822</string>
    <int name="Gdpr_Control" value="1" />
    <string name="NotificationHelper.Scheduled">273129443%7C1094985734</string>
    <int name="__UNITY_PLAYERPREFS_VERSION__" value="1" />
    <int name="Screenmanager%20Resolution%20Width" value="1080" />
    <string name="unity.cloud_userid">95a0dbb4b1309fcf094060f513e8d42d</string>
    <string name="playerData">%7B%22coins%22%3A5%2C%22OnCoinsChanged%22%3A%7B%7D%2C%22gems%22%3A0%2C%22OnGemsChanged%22%3A%7B%7D%2C%22hammers%22%3A0%2C%22OnHammersChanged%22%3A%7B%7D%2C%22adsRemoved%22%3Afalse%2C%22OnAdsRemovedChanged%22%3A%7B%7D%2C%22completedLevels%22%3A%5B1%2C2%2C3%2C4%2C5%2C7%2C8%2C9%2C10%2C11%2C12%5D%2C%22lastReachedLevel%22%3A13%2C%22lastPlayedLevel%22%3A13%2C%22hintsOpened%22%3A%7B%226%22%3A%5B0%5D%2C%227%22%3A%5B0%2C1%5D%7D%2C%22OnHintsOpenedChanged%22%3A%7B%7D%2C%22flags%22%3A%5B%22new_level_popup_showed%22%2C%22bonus_Bonus1_skipped%22%2C%22hint_LevelTricky1_0%22%2C%22hint_LevelTricky1_1%22%2C%22tricky_LevelTricky1_played%22%2C%22hint_Make_Potion_1%22%2C%22hint_Alchemy_1%22%5D%2C%22selectedPets%22%3A%7B%22cat%22%3A-1%2C%22dog%22%3A-1%2C%22bird%22%3A-1%2C%22frog%22%3A-1%7D%2C%22unlockedPets%22%3A%7B%22cat%22%3A%5B%5D%2C%22dog%22%3A%5B%5D%2C%22bird%22%3A%5B%5D%2C%22frog%22%3A%5B%5D%7D%2C%22activeActionLevel%22%3A1%2C%22LastReachedLevel%22%3A13%7D</string>
    <int name="Screenmanager%20Fullscreen%20mode" value="-1" />
    <int name="rate_shown" value="1" />
    <int name="shareClaimed" value="1" />
</map>

playerData里的coins就是电灯泡的数量,gems应该是钻石。coins%22%3A5,这个5就是数量,当前为5。


然后搜索dump.cs里的playerData,只有一个结果,那么这里就是就是破解的关键入口。其中get_Coins、get_Gems这两个都是比较常见的代码,如果你不知道关键字可以先搜这两个,看看有没有相关信息。

    // Methods
    // RVA: 0x13D954C Offset: 0x13D954C VA: 0x13D954C    public int get_Coins() { }
    // RVA: 0x13D14DC Offset: 0x13D14DC VA: 0x13D14DC    public void set_Coins(int value) { }
    // RVA: 0x13D9554 Offset: 0x13D9554 VA: 0x13D9554    public int get_Gems() { }
    // RVA: 0x13D75B4 Offset: 0x13D75B4 VA: 0x13D75B4    public void set_Gems(int value) { }
    // RVA: 0x13D955C Offset: 0x13D955C VA: 0x13D955C    public int get_Hammers() { }
    // RVA: 0x13D9564 Offset: 0x13D9564 VA: 0x13D9564    public void set_Hammers(int value) { }
    // RVA: 0x13D9604 Offset: 0x13D9604 VA: 0x13D9604    public bool get_AdsRemoved() { }
    // RVA: 0x13D960C Offset: 0x13D960C VA: 0x13D960C    public void set_AdsRemoved(bool value) { }
    // RVA: 0x13D96B8 Offset: 0x13D96B8 VA: 0x13D96B8    public int get_LastReachedLevel() { }
    // RVA: 0x13D96C0 Offset: 0x13D96C0 VA: 0x13D96C0    public void set_LastReachedLevel(int value) { }
    // RVA: 0x13D974C Offset: 0x13D974C VA: 0x13D974C    public void SetHintOpen(int level, int hintIndex) { }
    // RVA: 0x13D998C Offset: 0x13D998C VA: 0x13D998C    public bool GetHintOpen(int level, int hintIndex) { }
    // RVA: 0x13D9A88 Offset: 0x13D9A88 VA: 0x13D9A88    public void .ctor() { }


先来看Coins(),有两个结果,public int get_Coins()和public void set_Coins(int value),get_Coins根据地址跳转到0x13D954C,我查看了一下引用,结果为0,没有函数引用这个地方,如果修改这里的话十有八九是没什么用的。


接着继续看set_Coins,跳转到地址0x13D14DC。英文意思是设置、显示数值。破解过java的应该知道,方法名中有set_xx,get_xx,应该是改get,而不是set,set这个函数基本上有传入参数,所以不能直接修改这里,这里只是用来显示数值的。但是刚才的get找不到引用,所以只能另寻方法。



通过查看set_Coins的交叉引用,可以确定是哪里调用了显示,如上图红框,分别是几种扣费地址。随便选一个,汇编如下:

il2cpp:01324714 ; ---------------------------------------------------------------------------
il2cpp:01324714                 MOV             R0, #8       //函数地址
il2cpp:01324718                 LDR             R7, [R0]       //r0地址读取放入r7,此时r7是剩余的电灯泡
il2cpp:0132471C                 BL              HintPanel$$get_cost       //获取商品价格,需要花费多少电灯泡
il2cpp:01324720                 MOV             R6, R0       //把价格存入r6
il2cpp:01324724                 BL              sub_2B6FD4
il2cpp:01324728 ; ---------------------------------------------------------------------------
il2cpp:01324728
il2cpp:01324728 loc_1324728                             ; CODE XREF: sub_1324350+C4↑j
il2cpp:01324728                 SUB             R1, R7, R6       //r1=r7-r6,此时r1是剩余的电灯泡
il2cpp:0132472C                 MOV             R0, R5       //r5传入r0,然后进入BL
il2cpp:01324730                 MOV             R2, #0       //初始化
il2cpp:01324734                 BL              PlayerData$$set_Coins       //进行显示
il2cpp:01324738                 MOV             R0, #0
il2cpp:0132473C                 BL              DataManager$$get_playerData
il2cpp:01324740                 MOV             R5, R0
il2cpp:01324744                 CMP             R0, #0
il2cpp:01324748                 BNE             loc_1324750
il2cpp:0132474C                 BL              sub_2B6FD4
  v13 = DataManager__get_playerData(0);
  v14 = v13;
  if ( !v13 )
    sub_2B6FD4();
  v15 = *(v13 + 8);
  cost = HintPanel__get_cost();
  PlayerData__set_Coins(v14, v15 - cost, 0);
  v17 = DataManager__get_playerData(0);
  if ( !v17 )
    sub_2B6FD4();

伪代码比较容易理解,有个减号,很容易看出这是扣费的地方。

所以把5个函数的调用,全部由减法改成加法指令(add)即可越用越多,其它的货币也是一样的思路,就不一一举例了。

 

分享海报