linghujava 2013-04-26
郑重提示,转载请声明出处,多谢合作!
相信很多人对反编译有过兴趣,但是可能有部分人仅仅停留在通过反编译获取到apk里面的一些图片资源如style、layout等xml文件以作参考借鉴之用,可能也会有人用dex2jar+jd-gui的方式来看其java代码(如下图),但是大家都知道,这样的代码是不能回编译回去重新生成apk的,甚至代码也很容易被混淆,让你看得一头雾水
今天,我在这里就跟大家谈谈,希望通过这个博客,让大家知道,其实反编译是可以把apk反编译出来,然后修改里面的内容,然后再回编译,然后再签名,最后的结果是一个修改过的apk(当然是手机上能装的!)
大家都听过 Dalvik吧?帮大家百度了一下:
Dalvik是google专门为Android操作系统设计的一个虚拟机,经过专门针对移动平台深度优化过。Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式Dex。
当一个apk被反编译后,将会得到一个装满.smali文件的文件夹,而我们这次操作正是这些smail文件。
我们以目前 比较火的《宫爆老奶奶》为例子,讲述一下如何简单粗暴反编译一款游戏:
第一步:
首先,下载 《宫爆老奶奶》 的apk(以下简称apk),我这个是从豌豆荚下的,腾讯计费的版本,这里就省略传送了。
第二步:
下载反编译用的工具——apktool(传送门:http://download.csdn.net/detail/qq359948834/5116151),下载完后,随便解压放好。
第三步:
开始反编译。先将apk放到./ApkTool/文件夹里(./代表当前文件夹,这个路径随意,下同),然后打开./ApkTool/APKTool.cmd,按照窗口提示,要先重命名apk为123.apk,重命名完毕后,按‘1:反编译’然后回车,会看到反编译成功,得到./ApkTool/APK 文件夹。
第四步:
大家可以先随便浏览下文件夹里面的内容,会看到如图所示的文件:
很明显,这是混淆过的代码,但是不要紧,混淆只不过是一种浅层次的防反编译动作,只是对一些类进行了重命名而已,对应用的逻辑函数变量的用法还是一样的。
好,初步了解后,我们先不要改动任何文件,尝试再次打开./ApkTool/ApkTool.cmd,然后选择‘3:回编译并签名’看看能不能打回原版包。
如无意外,都会出现下面这种错误:
熟悉android开发的同学可能会知道是什么原因了,我菜鸟猜了一下,应该是当前apktool.jar所使用的android平台版本比较低,所以识别不了“layout-h500dp-large”和“layout-h500dp-normal”资源文件的命名,所以我们可以修改成“layout-large”和”layout-normal“,这样,我没记错的话,android2.2系统以上的都适用了。修改后,重新选择‘3’试试看,如果步骤没错,会出现以下结果:
*以上关于警告的信息我们可以先放放不予理会。
如果大家都是出现这种情况,说明反编译成功了,接下来,大家可以随意修改图片资源以及布局文件了,只要不影响命名,以及资源数量不变,就不会影响回编译,现在,我们进入重点部分——开始修改smail文件,以实现屏蔽扣费
第五步:
先锁定计费代码。怎么锁定计费代码呢?大家可以想一下计费sdk的架构和流程:
a.用户点击UI上的物品item
b.游戏底层通过jni方式回调java层请求计费
c.弹框提示购买内容及价格
d.监听用户点击动作,发送短信(或者支付宝等支付方式)或用户取消购买,jni回调游戏底层告知购买结果(一般只有两种结果,成功与否)
所以,以上流程大家有几种方法锁定:
1、使用UltrafileSearch(一款很出名的搜索工具)批量搜索支付常用关键字(pay、purchase、success、fail、notify等等)
2、从非内部类(文件名没有$)入手,扫读所有方法名,碰到敏感方法,可以尝试添加smail代码输出log信息已判定该方法在什么时候执行
3、从与支付有关的资源入手,看看程序哪里调用了支付用到的图片和文字资源(老奶奶的资源都是写在asset,然后把所有调用支付界面的代码打成so,所以我们反编译后看不到,这个方法不适用于老奶奶这款游戏)
经过一轮苦苦摸索,终于锁定了ITencent.smali这个文件,里面有MakePay(I)V,PrePay(I)V,SendSMSCB(ILjava/lang/String;)Z 这几个很明显的方法,大家可以在这几个方法里面添加log语句(具体怎么添加,如图所示,如果还有疑问,请先阅读smail的相关语法),然后看看logcat的输出情况:
贴上代码:
.method public static MakePay(I)V .locals 3 .parameter "id" .prologue #========== .line 8888 const-string v0, "kelly" const-string v1, "========MakePay" invoke-static {v0,v1},Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I #==========
*其他方法类似,这里不作赘述(注意!这里的.locals 3指的是当前方法使用了3个寄存器,因为我们的log语句一般都用到了两个寄存器,所以,当使用log时,请先留意下该方法的.locals 值是不是>=2)
修改完毕后, 直接打开反编译工具选择‘3’回编译后,再安装到手机跑跑看看logcat显示(当然,也可以直接使用‘9:一键回编译并安装’,前提是你的adb环境要搭好),如图所示:
游戏界面:
logcat如图:
*android开发者都知道,log有两个参数,一个tag一个是String型的content,大家可以发挥一下小宇宙,输出所有String型的内容看看
看到这log,我们成功一半了,说明计费代码锁定了
第六步:
修改计费逻辑。这一部分涉及不少smail语法,如果大家觉得迷茫,可以先暂停下来,看看有关smail语法的介绍。
大家看看MakePay方法里面,有一行代码:
invoke-static {p0, v0}, Lcom/tencent/webnet/WebNetInterface;->SMSBillingPoint(ILjava/lang/String;)V
略懂英语的应该也能猜到什么意思:“短信,购买,点 ”,连起来差不多意思就是生成计费短信,并提示用户付费,我们进入该方法所在的路径看个究竟(com/tencent/webnet/WebNetInterface)
如图,大家也可以在这方法加上log:
.method public static SMSBillingPoint(ILjava/lang/String;)V .locals 7 .parameter .parameter .prologue #========== .line 8888 const-string v0, "kelly" const-string v1, "========SMSBillingPoint" invoke-static {v0,v1},Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I #==========
看到这里,大家开始头晕了吧?hold住,距离成功就差一点点了!
大家看看这个方法里面,有一句代码:
invoke-static {v0, v5, v1}, Lcn/emagsoftware/gamebilling/api/GameInterface;->doBillingBySms(Ljava/lang/String;ZLcn/emagsoftware/gamebilling/api/GameInterface$BillingCallback;)V
意思也很易读懂:doBillingBySms=“做,购买,通过,短信”,而且,大家再看,这个方法参数之中有一个字符串,一个int整形,以及一个回调方法BillingCallback,所以,我可以肯定,这是一个调用发短信被回调发送结果的方法!但是,大家也看到,这句代码藏在这个SMSBillingPoint方法里面的底部,也就是说,你要调用它,必须经过重重逻辑判断。
那么,现在你有两种选择:1、读懂所有逻辑,修改每个逻辑的条件,以达到doBillingBySms语句所在行; 2、直接把doBillingBySms语句提前到所有逻辑之前
很明显,我会选择第二种
但是,在选择第二种方法,也意味着,你要提供doBillingBySms的三个参数。
细心一看,你会发现,这三个参数中的第二个,也就是说v5,在方法一开始就定义了,所以参数已解决一个;
v0是什么?大家请看:
invoke-static {v1, v2}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; move-result-object v0 .line 221 new-instance v1, Ljava/lang/StringBuilder; const-string v2, "IDO send sms code = " invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
里面与v0有关的都是些字符串操作,比如StringBuilder,所以,我猜v0是一个让运营商识别所购买道具的识别码,而v1,是一个回调函数的实例,它的实例化代码为,大家去这个路径写一下log输出,对后面的步骤有用(步骤和注意点跟上面提到的如何写log一致):
new-instance v1, Lcom/tencent/webnet/q; invoke-direct {v1}, Lcom/tencent/webnet/q;-><init>()V
好!现在,三个参数都拿到手了,我整理一下,用以下内容,直接覆盖这个SMSBillingPoint方法:(其实我个人不太推荐删减源文件的所有代码,毕竟以后可能会有用,所以建议只把我们的代码插入到方法里的头部,然后在我们的代码的尾部加一个return-void即可)
.method public static SMSBillingPoint(ILjava/lang/String;)V .locals 7 .parameter .parameter .prologue #========== .line 8888 const-string v0, "kelly" const-string v1, "========SMSBillingPoint" invoke-static {v0,v1},Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I #========== const/16 v6, 0x3ed const/4 v5, 0x1 const/4 v4, 0x0 const/4 v0, 0x0 sget-object v1, Lcom/tencent/webnet/b;->ah:Lcom/tencent/webnet/WebNetInterface; iput-object p1, v1, Lcom/tencent/webnet/WebNetInterface;->m_SMSCurMark:Ljava/lang/String; .line 220 const-string v1, "%03d" new-array v2, v5, [Ljava/lang/Object; add-int/lit8 v3, p0, -0x1 invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer; move-result-object v3 aput-object v3, v2, v0 invoke-static {v1, v2}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; move-result-object v0 new-instance v1, Lcom/tencent/webnet/q; invoke-direct {v1}, Lcom/tencent/webnet/q;-><init>()V invoke-static {v0, v5, v1}, Lcn/emagsoftware/gamebilling/api/GameInterface;->doBillingBySms(Ljava/lang/String;ZLcn/emagsoftware/gamebilling/api/GameInterface$BillingCallback;)V return-void .end method
替换好之后,我们回编译一下,看看有没有报错(报错的同学,麻烦仔细检查一下上面提到的.local有没有注意,而且也要很仔细地检查代码有没有写错),然后打开logcat,在游戏里面点击购买,看看输出情况:
很好,这下说明我们的apk即使已经动过计费逻辑,也能跑起来了
下面,是最后一步,也是最简单的一步,就是把回调失败改成回调成功,怎么改呢?大家看看com/tencent/webnet/q的内容:
.method public onBillingFail()V .locals 3 .prologue .line 233 sget-object v0, Lcom/tencent/webnet/b;->am:Lcom/tencent/webnet/o; const/16 v1, 0x3e9 sget-object v2, Lcom/tencent/webnet/b;->ah:Lcom/tencent/webnet/WebNetInterface; invoke-static {v2}, Lcom/tencent/webnet/WebNetInterface;->a(Lcom/tencent/webnet/WebNetInterface;)Ljava/lang/String; move-result-object v2 invoke-virtual {v0, v1, v2}, Lcom/tencent/webnet/o;->a(ILjava/lang/String;)V .line 234 return-void .end method .method public onBillingSuccess()V .locals 3 .prologue .line 227 sget-object v0, Lcom/tencent/webnet/b;->am:Lcom/tencent/webnet/o; const/16 v1, 0x3e8 sget-object v2, Lcom/tencent/webnet/b;->ah:Lcom/tencent/webnet/WebNetInterface; invoke-static {v2}, Lcom/tencent/webnet/WebNetInterface;->a(Lcom/tencent/webnet/WebNetInterface;)Ljava/lang/String; move-result-object v2 invoke-virtual {v0, v1, v2}, Lcom/tencent/webnet/o;->a(ILjava/lang/String;)V .line 228 return-void .end method
怎么改?不用教吧?直接把onBillingSuccess的内容复制粘贴,替换onBillingFail方法原有的内容(其实细心的同学可以发现,两个方法的区别就在于那个字符串“ const/16 v1, 0x3e9”和“ const/16 v1, 0x3e8”而已)
大功告成,现在打开反编译工具,回编译后安装到手机,试试看吧(注意!为保险起见,建议各位断网+飞行模式,你懂的)
写在最后:
作为一个开发者,我们要尊重每一个应用,无论游戏好玩不好玩好不好用,它都代表着一个开发者全部的心血,赞之合理,踩之有度。
至于软件扣费这个情况,无可否认,对于已经习惯免费的国人来说,一开始可能不习惯,但是,试想下,一款优秀软件,只需要20块钱左右,就已经可以买到很不错的道具了,而且有更加好的游戏体验及乐趣,再加上,这些钱能帮助到开发者开发出更多更好玩的游戏更精彩的应用,这是一个良性循环,希望玩家、开发者、用户能一起构建这种氛围。
附件为一个已经破解了的《宫爆老奶奶》,仅供参考,请支持正版游戏!下载后请在24小时内自觉删除,多谢合作!
使用的是微盘分享:Hi,推荐文件给你 "宫爆老奶奶-破解版.apk" http://vdisk.weibo.com/s/z2r45