之前做了一段时间的安卓手游破解正版验证工作,总结一些基本的方法和知识,便于分享交流。破解的目的是让国内一些“被阉割”过(无GooglePlay框架)的安卓手机也能畅快地玩儿上GooglePlay上的游戏,所以文章主要讨论海外游戏市场下载的apk包如何去除正版验证。国内的手游大部分还是做了防破解工作的,加壳、防反编译等等(此篇不予讨论)。大概海外市场的氛围较好,所以GooglePlay、Amazon商店的游戏基本都可以反编译成功,顶多加了代码混淆、调用了GP/Amazon的验证SDK而已。
适合谁看?
破解初学者、懂一些编程知识的童鞋(完全不懂编程也可以,只能破解那些只需要替换一些文件/文本的游戏)、以及对apk破解/安全性维护感兴趣的童鞋。
什么是Smali?
先说Dalvik是google专门为Android操作系统设计的一个虚拟机,经过深度的优化。虽然Android上的程序是使用java来开发的,但是Dalvik和标准的java虚拟机JVM还是两回事。Dalvik VM是基于寄存器的,而JVM是基于栈的;Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码。Dalvik VM比JVM速度更快,占用空间更少。
通过Dalvik的字节码我们不能直接看到原来的逻辑代码,这时需要借助如Apktool或dex2jar+jd-gui工具来帮助查看。但是,注意的是最终我们修改APK需要操作的文件是.smali文件,而不是导出来的Java文件重新编译(况且这基本上不可能)。
详细的Smali语法学习可以参考这篇文章:http://blog.csdn.net/lpohvbe/article/details/7981386,里面详细介绍了Smali中的数据类型、方法调用等等。
此外,反编译过程中遇到不懂的关键字,可参考http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html,非常实用的文档。
如何去除正版验证?
你需要的工具
工欲善其事,必先利其器,反编译工具很多,针对点也不同,本文主要用的是APKIDE(也叫APK改之理,一个类似APK studio的可视化反编译软件,内部已经集成了dex转smali、dex转jar、重新编译、签名、搜索等功能,推荐使用),其他工具和功能如下:
apktool,命令行工具,可以decode和build apk文件,主要用来解析xml等资源文件。
dex2jar,一系列命令行工具,能将文件在dex、jar和smali之间相互转换,反编译时可以dex转为smali后修改smali代码,再将smali转回成dex。
jd_guid,可视化工具,将jar文件反编译为java代码,便于阅读代码逻辑,至于直接编辑java代码什么的,你想多了。
baksmali,命令行工具,也是用来转换dex和smali文件。
apksigner,命令行签名工具,反编译重新打包之后用来重新签名apk。
apk studio,类似APK IDE,英文界面,有时候会反编译失败而APK IDE不会,猜想可能是内嵌的baksmali之类的版本不一样或者编码问题吧。
以上工具在百度、谷歌上下载都可搜到下载。
常用方法
通常不能运行的游戏表现有这几种:
1. 提示未安装谷歌框架,不能游戏;或者提示谷歌验证此游戏为盗版(可能你的apk不是从GooglePlay下载而来)。
2. 提示当前账号并未购买此游戏(同样,可能此游戏并非GooglePlay上购买后直接下载的)
你打开游戏可能会卡在这样的页面:
第一种情况是因为代码中调用了Google官方的验证SDK,仅验证游戏是否来自GooglePlay,而第二种添加了Google账号验证,检查你是否购买了此游戏,这两种情况都可以通过绕开Google验证来破解:
去除GooglePlay验证(可写成脚本进行自动化破解)
最简单的思路其实是找到代码中调用验证的地方,将其注释掉,直接调用callback函数(一般此类SDK都是异步返回,开发者实现一个带callback的Listener)中验证成功的方法,拷贝复制即可。至于调用的地方,一般都在入口Activity类中,函数名一般带有verify、certificate、auth之类的字样。这也的确是个可行的办法,但用这个办法破解了几款游戏后发现游戏之间差异很大,你必须针对每款游戏去找调用入口,太费时了,有没有规律性的、可自动化的方法呢?
答案是肯定的,既然是SDK进行的验证,我们只修改SDK内的代码不就好了吗?只要验证的结果每次都返回成功就OK了。按Google文档上的教程,接入验证使用的是sdk\extras\google\play_licensing下的sdk工程,引入后就一个包:com.google.android.vending.licensing,用APK IDE打开如下:
其内部大致做的事情就是验证apk签名,请求Google服务器检查验证是否通过、并得到游戏的扩展数据包信息(obb文件名、大小等),其中夹杂超时处理、缓存过期时间等等。
找到了负责验证的sdk包,修改哪里就可以强制返回成功了呢?一个是LicenseValidator,负责解析Google服务器返回的responseCode,另一个是实现Policy接口的类,负责解析从服务器获取的额外信息(扩展包文件等)。这里偷了一点懒,没仔细研究SDK,而是找到个市面上比较好用的安卓破解器:幸运破解器(装在手机上就能直接破解游戏的神器,还能破解内购),用它破解一款游戏后导出apk,反编译出smali文件,写个脚本批量对比前后文件变化,过滤掉注释等无用信息得到的就是我们要修改的地方了!总结后如下:
1. 工程中所有调用java/security/Signature;->verify的地方,将下一行的
move-result v3
改为
const/4 v3, 0x1
move-result v3的意思是将verify函数的返回结果赋值给v3,我们直接替换成声明一个值为true的v3变量即可。verify函数用以验证签名,我们在破解后必然要重新签名,无法保留原有开发者签名,所以所有验证签名的地方都需要进行这一步修改。
2. 实现了Policy接口的类(一般是APKExpansionPolicy和ServerManagedPolicy)中的函数allowAccess,将函数开头的声明
const/4 v1, 0x0
改为
const/4 v1, 0x1
(其中v1命名不确定,可能叫v0或v2等等)此函数根据上次请求结果和重试次数判断是否验证通过,v1是默认返回值,初始化时为false,后续代码判断满足某些条件后将v1赋值为true,函数退出时必然返回v1。所以我们将v1默认值设为true就相当于默认验证通过了。
注意,这里可能v1默认值已经是true了,而是在判断验证未通过时将v1赋值为false再返回,因此可以将此函数所有的变量声明都改为默认值为true。
3. smali\com\google\android\vending\licensing\LicenseValidator.smali中的函数verify,将
0x1 -> :sswitch_1
0x2 -> :sswitch_0
改为
0x1 -> :sswitch_0
0x2 -> :sswitch_1
这里是将switch中验证失败的情况指向了验证成功时要执行的代码段。
4. 为了以防万一,可以在继承了Lcom/google/android/vending/licensing/LicenseCheckerCallback接口的类中修改dontAllow函数,在函数一开始加入:
const/16 v1, 0x100
invoke-interface {p0, v1}, Lcom/google/android/vending/licensing/LicenseCheckerCallback;->allow(I)V
return-void
这里的意思是直接调用listener的allow函数,p0是callback自己,变量v1声明的初始值为0x100,是代表成功的常量,调用p0的allow函数,参数为v1,然后直接返回,之后的代码就不会被执行了。这样就确保必然调用allow,万无一失。
5. 完成以上的步骤就可以通过GooglePlay验证了,但打开游戏后会开始下载游戏扩展包(obb文件),强制忽略手机上已存在的obb文件。这是因为正常的sdk在验证的同时会返回此游戏最新的扩展包名、包大小等信息,方便用户下载、开发者更新等。因此为了可以顺畅游戏,还要在实现了Lcom/google/android/vending/licensing/Policy接口的类中修改getExpansionFileName等一系列函数,直接将游戏扩展包的信息写在代码中直接返回。
完成以上步骤即可顺利游戏了,因为这些步骤都是有迹可循、规律的,所以写个脚本就可以一键破解了(脚本正在编写中)。
至于为什么不用破解后的验证sdk直接覆盖替换其他游戏的sdk,是因为各自游戏开发时间不同,采用的sdk版本不同,直接覆盖可能会导致有些方法找不到,而以上的这些修改都是在sdk核心逻辑中,亲自验证了几个版本发现这些核心逻辑并没有改变。
可能遇到的特殊处理
有些游戏可能在进行完以上步骤会出现闪退、报错等情况,这可能是由于该游戏接入的第三方工具在捣乱。以“拔拔曼陀罗”为例,本人完成以上步骤后报错NullPointer,遂打开Eclipse查找Logcat(不得不说查Logcat是非常实用的办法,只要眼疾手复制出来即可),发现的错误如下:
1. 无com.android.vending.CHECK_LICENSE权限错误:游戏中有个corona的包,搜索后得知这是个打包插件,方便开发者集成Google、Amazon等sdk、方便build。在Google验证处调用enforceCallingOrSelfPermission时报错,但Manifest中已添加此权限,未找到原因,反正已经破解了验证,直接注释掉,运行通过。
2. 空指针报错:同样还是Corona的坑,在获取谷歌框架服务时未判断null,大概corona的开发者觉得全世界的安卓机都应该有谷歌框架吧,呵呵,注释后运行成功。
当然需要特殊处理的地方肯定会很多,并不是所有游戏都能自动化破解,但有了自动化脚本就已经节省不少工作量了。
那些难以跨越的坑
1. 代码混淆
混淆后的代码几乎找不到从哪个类下手、从哪个方法下手,因为放眼望去所有类名、方法名全部都是a、b、c。海外市场也有不少游戏和应用添加混淆,但这也仅仅是“几乎”而已,并不是完全没办法,具体如何破解下一篇再来阐述。
2. 加壳等防反编译措施
还有很多种办法可以防止apk被反编译,如字符串混淆让反编译后的String类型不可读、用花指令或动态加载等方式让反编译看不到源码甚至无法反编译源代码。所幸目前见到的海外市场游戏较少采取此种措施,无需对此进行破解。