最近代码码多了于是打算翻翻有什么游戏可玩,无意之中翻到了之前玩的元气骑士,想到还有许多人物都没有解锁,但是又不想攒金币因为太慢了,况且还有一些角色是要RMB才能买的,通过PKiP发现无加固壳,于是一怒之下打算破解其内购。


0x01 找寻突破口

  打开元气骑士进入购买人物的场景,点击购买,因为钻石不够所以点进去发现跳转到了购买钻石的页面。1块钱可以兑换800钻石,于是点购买跳转到了爱贝支付。返回取消购买回到了游戏入口界面,提示购买失败。

0x02 初步尝试破解

  于是用MT2拿到apk的dex文件,搜索“失败”,发现iapppay下的IAppPayOrderChecker和MainActivit$3$1格外瞩目。进入IAppPayOrderChecker发现没有相关内容,于是转到MainActivity$3$1中。在OnPayResult函数中发现如下代码块:

    .line 113
    iget-object v1, p0, Lcom/chillyroomsdk/iapppay/MainActivity$3$1;->this$1:Lcom/chillyroomsdk/iapppay/MainActivity$3;
    iget-object v2, p0, Lcom/chillyroomsdk/iapppay/MainActivity$3$1;->val$orderId:Ljava/lang/String;
    iget-object v3, p0, Lcom/chillyroomsdk/iapppay/MainActivity$3$1;->val$extra:Ljava/lang/String;
    invoke-virtual {v1, v2, v3}, Lcom/chillyroomsdk/iapppay/MainActivity$3;->onPayFail(Ljava/lang/String;Ljava/lang/String;)V
    .line 116
    :goto_18
    const-string v1, "Unity" 

  其中的OnPayFail值得注意,既然有OnPayFail那么就应该有OnPaySuccess,我们继续往下翻的确找到了OnPaySuccess,同时在OnPaySuccess上方看到 :pswitch_45,将其记录下来。翻到底部找到了一个pswitch结构:

     .line 96
    :pswitch_data_86
    .packed-switch 0x0
        :pswitch_45
        :pswitch_4
        :pswitch_7c
    .end packed-switch 

  注意到:pswitch_45下方还有:pswitch_4和:pswitch_7c,我们找到pswitch_4所在的位置发现正好跳转到了OnPayFail所在的位置。这时我们大概清楚了代码逻辑,即不同的情况分别跳转到不同的:pswitch_代码块。我们将另外两个pswitch的跳转编号全部修改为45。到这里,购买成功的破解就完成了。

0x03 深入订单校验

  但是进入游戏发现仍然购买失败。提示的是:支付成功但验签失败。

  看来元气骑士的内购验证还是比较严密的。回到dex文件,发现该字符串仍旧位于MainActivity$3$1,由:cond_6e跳转而来,发现原来在OnPaySuccess的上一步还做了一个支付判断(if-eqz v0, :cond_6e),如果订单状态校验不吻合就跳转到:cond_6e。将这条代码注释掉,重新进入游戏购买,发现还是失败了,提示订单支付失败。若发生丢单请勿重复支付,并联系客服。

  这时冷静下来,猜想除了 MainActivity 内的 OnPayResult 判断外,应该还有其他的订单检验方法,重新审视了一下com类,不出所料,在最开始被忽视的 IAppPayOrderChecker 中发现了对应字符串,代码块对应 :cond_f5,往上一翻发现多处if-eqz跳转到了此处!二话不说对所有转到 :cond_f5 的判断语句进行注释,中途还发现一个 :cond_133 跳转到验证失败于是也一并注释了。 至此不出意外应该已经成功完成了内购破解。

0x04 破除apk签名验证

有意思的是,虽然内购破解应该已经顺利完成了,但是重编译并自己手动打上签名安装后发现直接无法进入游戏了,提示我们请下载正版游戏并跳转到了元气骑士的官方网站。猜想是联网验证于是断掉无线局域网重进游戏,发现依旧无法进入游戏并直接闪退了,看来游戏做了联网+本地的双重验证。 回到dex++,最终在 UnityExtendAcitivity 中的起始位置发现了名为 GetKeyHash 函数,代码片段如下:

 .method public static GetKeyHash()Ljava/lang/String;
    .registers 7
    .prologue
    const/4 v3, 0x0
    .line 26
    :try_start_1
    sget-object v4, Lcom/unity3d/player/UnityPlayer;->currentActivity:Landroid/app/Activity;
    invoke-virtual {v4}, Landroid/app/Activity;->getPackageManager()Landroid/content/pm/PackageManager;
    move-result-object v4 

在.prologue与const/4 v3, 0x0之间直接插入return-void拦截此函数的执行,最后保存修改过的dex文件,更新apk包并打上应用签名,重新安装apk文件,没有出现异常,可以进入游戏了,随便购买一个人物,进入订单页面,返回取消订单,可以看到支付成功,除了人物之外,钻石也可以随意购买了。

支付成功

0x05 附件与声明

破解后的游戏下载地址:https://pan.baidu.com/s/1pL1JzsR 密码: 7dvq

本文及附件内容仅限于技术学习,同时希望相应公司尽快修复相关漏洞。