使用Android killer对安卓游戏进行反编译实现内购

本次目的是回顾一遍安卓反编译流程,以供最近深入学习。

TIM截图20190816205121.png

    下图便是本次破解的目标程序,是我从52破解论坛随便找的一个单机游戏。

        首先我们整理一下思路:

    想要破解内购,我们只需要找到游戏中判断是否支付成功的类就足够了。那么我们全部流程实际上都是围绕着寻找这个类来做各系列的操作。

    很显然,既然我们要寻找是否支付成功的类,我们只需寻找充值的入口,然后根据充值时候提示的信息,进行搜索就可以找到这个类。我们来假设我们就是这个游戏的开发人员,猜一下充值的主要逻辑:

    1.打开充值的接口(一般都是打开支付宝、微信、话费,等等)

    2.用户支付

    3.判断接口返回值,一般是否支付成功接口都会返回一个值,供程序判断是否支付成功,如果支付成功那么就提示支付成功,并给游戏充值,反之则是提示充值失败。

    好了,整体充值逻辑就是这样,所以我们破解的第一步就应该是寻找这个判断是否支付成功的类,如果我们将这个判断条件给翻转,是否就是充值成功了呢?答案是当然。

     那么就直接进入破解。

     将目标APP拉入Android Killer工具中并进行反编译。

TIM截图20190816204145.png

    分析成功后,这个时候有一个好习惯就是——将APP危险权限删除掉,我们可以在上图的User Permission(权限)中看到几条带着红色的权限,这些都是危险权限,例如上图的SEND_SMS这条就是发送短信。

    打开Android Manifest.xml 清单文件中删除对应的权限即可。

    基础工作做的差不多了,那么就直接在模拟器中运行APP,寻找关于“充值”类的线索吧。

TIM截图20190816204016.png

    然而..我们的目标程序估计是开发人员跑路了(笑),充值系统并不能正常工作,无法提供有效的信息,那么我们直接猜吧。

    一般情况下充值成功之后都会提示一条信息,如:“充值成功”、“成功”、等等,那么就直接搜索(搜索的时候需要把字符串转换成Unicode格式,如:“成功”=“\u6210\u529f”)。

TIM截图20190816211705.png

    搜索到两个文件,c.smali/e.smali。

    (Smali格式为反编译APP后的代码文件,并不是java语言,我们可以通过工具将其翻译成java,当然,一般的我们可以简单的学习一下这个语言的语法。)   

    得到字符串“模拟支付成功”,虽然字符串看起来怪怪的,不管他,看下他的代码。

    打开c.smali文件,并翻译java代码。

image.png

image.png

    阅读代码得知,这个并非是判断支付是否成功,而是一段AlertDialog(提示框)的函数,鬼知道为什么写这个东西。我们再打开e.smali并查看代码。

onSUsezz.png

TIM截图20190816214617.png

    最后..很显然,也不是哈哈哈哈哈哈哈哈哈哈。
    好了好了,这个是第一个思路,反编译的过程本来就是枯燥的,通过无数的手段进行缩小目标函数(严肃)。

    第二个思路,搜索他的方法。一般的,程序员必须掌握的一个技能就是:代码命名规范。目的是,为一起的开发人员或后续人员提供便利,能够快速的阅读代码。当然,也给我们破解人员提供了便利(笑)。
    一般的,命名规范中有一条最重要的就是——使用英文对函数进行命名。可能你已经猜到了这个思路,我们使用Android Killer工具中的方法声明进行搜索:“Playment”、“Ply”(支付)类似的名称,并注意大小写。得到下面几个类,我们依次进行翻译成java代码分析。
   
这里就不再阐述,最后锁定到d.smali 文件。

    到这一步就比较简单了,替换各个case,将case 1下面的代码复制到其他的case中即可达到内购的破解。也就是说,无论是充值失败、还是充值取消,全部都是篡改到充值成功。
    这个时候我们切换成smali代码中来,java代码只是一个用来快速浏览的工具,我们并不能直接使用java语言进行篡改(私以为能够翻译成java,那么通过java自然可以翻译回smali代码,具体为什么我也不知道,摊手)。
    

packed-switch v0, :pswitch_data_0
    goto :goto_0
    :pswitch_0
    iget-object v0, p0, Lcom/snowfish/cn/ganga/offline/sf/d;->a:Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;
    const-string v1, ""
    invoke-virtual {v0, v1}, Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;->onSuccess(Ljava/lang/String;)V
    goto :goto_0
    :pswitch_1
    iget-object v0, p0, Lcom/snowfish/cn/ganga/offline/sf/d;->a:Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;
    const-string v1, ""
    invoke-virtual {v0, v1}, Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;->onCanceled(Ljava/lang/String;)V
    goto :goto_0
    :pswitch_2
    iget-object v0, p0, Lcom/snowfish/cn/ganga/offline/sf/d;->a:Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;
    const-string v1, ""
    invoke-virtual {v0, v1}, Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;->onFailed(Ljava/lang/String;)V
    goto :goto_0
    nop
    :pswitch_data_0
    .packed-switch 0x1
        :pswitch_0
        :pswitch_1
        :pswitch_2
        :pswitch_2
    .end packed-switch

    通过阅读smali代码,得知,这一段便是switch的全部代码,语法跟java区别很大,不过整体结构依然是各个case。

:pswitch_1
    iget-object v0, p0, Lcom/snowfish/cn/ganga/offline/sf/d;->a:Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;
    const-string v1, ""
    invoke-virtual {v0, v1}, Lcom/snowfish/cn/ganga/offline/helper/SFIPayResultListener;->onCanceled(Ljava/lang/String;)V
    goto :goto_0

    这个便是一个case,那么我们跟java代码对比一下。

case 1: 
      this.a.onSuccess("");
      return;

    基本结构都一样,我们只需要将各个“:pswitch_1”下的代码修改一下即可。
    然后左上角进行编译便完成。

TIM截图20190816224103.png

TIM截图20190816225009.png

    最后,编译后在APP/模拟器中运行查看效果。
    已经破解成功。

    最后:
    游戏链接: https://pan.baidu.com/s/1P4IHumtFoR8PwXJZdXRYXw 提取码: cus5 (包含原始版本和破解后的版本)。
    实际上编译后我遇到了问题:“APP编译失败,无法进行下一步签名”,
    这个是因为我下载的Android killer本身编译器的问题。之后通过basksmali工具进行重新对classes.dex文件进行编译,后续会更新basksmali工具的使用。