本次目的是回顾一遍安卓反编译流程,以供最近深入学习。
下图便是本次破解的目标程序,是我从52破解论坛随便找的一个单机游戏。
首先我们整理一下思路:
想要破解内购,我们只需要找到游戏中判断是否支付成功的类就足够了。那么我们全部流程实际上都是围绕着寻找这个类来做各系列的操作。
很显然,既然我们要寻找是否支付成功的类,我们只需寻找充值的入口,然后根据充值时候提示的信息,进行搜索就可以找到这个类。我们来假设我们就是这个游戏的开发人员,猜一下充值的主要逻辑:
1.打开充值的接口(一般都是打开支付宝、微信、话费,等等)
2.用户支付
3.判断接口返回值,一般是否支付成功接口都会返回一个值,供程序判断是否支付成功,如果支付成功那么就提示支付成功,并给游戏充值,反之则是提示充值失败。
好了,整体充值逻辑就是这样,所以我们破解的第一步就应该是寻找这个判断是否支付成功的类,如果我们将这个判断条件给翻转,是否就是充值成功了呢?答案是当然。
那么就直接进入破解。
将目标APP拉入Android Killer工具中并进行反编译。
分析成功后,这个时候有一个好习惯就是——将APP危险权限删除掉,我们可以在上图的User Permission(权限)中看到几条带着红色的权限,这些都是危险权限,例如上图的SEND_SMS这条就是发送短信。
打开Android Manifest.xml 清单文件中删除对应的权限即可。
基础工作做的差不多了,那么就直接在模拟器中运行APP,寻找关于“充值”类的线索吧。
然而..我们的目标程序估计是开发人员跑路了(笑),充值系统并不能正常工作,无法提供有效的信息,那么我们直接猜吧。
一般情况下充值成功之后都会提示一条信息,如:“充值成功”、“成功”、等等,那么就直接搜索(搜索的时候需要把字符串转换成Unicode格式,如:“成功”=“\u6210\u529f”)。
搜索到两个文件,c.smali/e.smali。
(Smali格式为反编译APP后的代码文件,并不是java语言,我们可以通过工具将其翻译成java,当然,一般的我们可以简单的学习一下这个语言的语法。)
得到字符串“模拟支付成功”,虽然字符串看起来怪怪的,不管他,看下他的代码。
打开c.smali文件,并翻译java代码。
阅读代码得知,这个并非是判断支付是否成功,而是一段AlertDialog(提示框)的函数,鬼知道为什么写这个东西。我们再打开e.smali并查看代码。
最后..很显然,也不是哈哈哈哈哈哈哈哈哈哈。
好了好了,这个是第一个思路,反编译的过程本来就是枯燥的,通过无数的手段进行缩小目标函数(严肃)。
第二个思路,搜索他的方法。一般的,程序员必须掌握的一个技能就是:代码命名规范。目的是,为一起的开发人员或后续人员提供便利,能够快速的阅读代码。当然,也给我们破解人员提供了便利(笑)。
一般的,命名规范中有一条最重要的就是——使用英文对函数进行命名。可能你已经猜到了这个思路,我们使用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”下的代码修改一下即可。
然后左上角进行编译便完成。
最后,编译后在APP/模拟器中运行查看效果。
已经破解成功。
最后:
游戏链接: https://pan.baidu.com/s/1P4IHumtFoR8PwXJZdXRYXw 提取码: cus5 (包含原始版本和破解后的版本)。
实际上编译后我遇到了问题:“APP编译失败,无法进行下一步签名”,
这个是因为我下载的Android killer本身编译器的问题。之后通过basksmali工具进行重新对classes.dex文件进行编译,后续会更新basksmali工具的使用。