最近抽空研究了下android 加壳技术,发现关于加壳的源代码特别少,即使有也不能做到版本兼容,问题又特别多,对app加壳感觉远没有当初想的那么简单,而现阶段成熟的加密软件多是收费的,爱加密,梆梆加固等。收费就暂不考虑了,咱们只能曲线救国,也试过腾讯御安全,认证通过了,也能查看安全报告,就是无法加固,试了很久一直再刷新,最终也没有加固成功;试过网易云易盾,只是申请一下试用,打了几个电话做广告,最终也没有同意申请。第三方加密平台没有money看来是行不通了!
我们都知道,常见的APP加密方法包括伪加密、混淆、运行验证和第三方加密平台APP加密。
伪加密
伪加密是Android4.2.x系统发布前的加密方式之一,通过java代码对APK(压缩文件)进行伪加密,其修改原理是修改连续4位字节标记为”P K 01 02”的后第5位字节,奇数表示不加密偶数表示加密。虽然伪加密可以起到一定防破解作用,但也会出现问题,首先使用伪加密对其APK加密后市场无法对其进行安全检测,导致部分市场会拒绝这类APK上传;其次,伪加密的加密方式和解密方式也早已公布导致它的安全程度也大大降低;再次,Android4.2.x系统无法安装伪加密的APK;最后伪加密只是对APK做简单保护,在java层源码加壳保护、核心so库、资源文件、主配文件、第三方架包方面却没有任何保护处理。
注意:高版本不支持这样的方法,所以还是不要尝试使用这样的加密方式了。现在Android 版本系统一般都是4.4以上的了,高版本也不支持,pass掉该加密方式。
验证
APP加密之运行时验证,主要是指在代码启动的时候本地获取签名信息然后对签名信息进行检验来判断自己的应用是否是正版,如果签名信息不是正版则提示盗版或者直接崩溃。当然你可以把必要的数据放在服务器端。破解:找到smali文件中,判断是否相等的部分。改为常量true,即失效。总之,反编译一些apk之后,只要是java代码写的总会有smil文件。对于smil文件,如果耐心读的话,还是可以查看到一些关键代码的。
混淆
APP加密之混淆,混淆是把原来有具体含义的类名,变量名,方法名,修改成让人看不懂的名字,例如方法名getUserName编程了方法名。代码混淆只是增加APP代码的阅读难度,对APP安全起不到实质的作用,但其是APP加密之前的一个必要步骤。
个人觉得混淆还是比较有用的,加密之前提供初步的保护,就稍微研究了一下。
初步配置
大致:构建类型有debug 和release版本,我们在发布版本的时候指定 minifyEnabled=true; 设置启用代码混淆,混淆规则记录在proguard-rules.pro这个文件中,
buildTypes{
release{
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}
}
指定混淆规则
混淆规则是用来指定是否混淆的规则,听起来像废话,在这个proguard-rules.pro中我们需要指定不需要混淆的类。哪些不需要混淆呢,我们分为基本指令区和定制指令区,基本指令区包括如下部分:
#-----------------------------基本指令区-------------------------------------------------------------
-optimizationpasses5 #指定代码的压缩级别0-7
-dontusemixedcaseclassnames #是否使用大小写混合
-dontpreverify #混淆时是否做预校验
-verbose# 混淆时是否记录日志
-optimizations!code/simplification/arithmetic,!field/*,!class/merging/* #混淆时所采用的算法
-printmapping proguardMapping.txt
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#-------------------------四大组件默认保留区---------------------------------------------------------
-keeppublic class * extends android.app.Activity
-keeppublic class * extends android.app.Application
-keeppublic class * extends android.app.Service
-keeppublic class * extends android.content.BroadcastReceiver
-keeppublic class * extends android.content.ContentProvider
-keeppublic class * extends android.app.backup.BackupAgentHelper
-keeppublic class * extends android.preference.Preference
-keeppublic class com.android.vending.licensing.ILicensingService
-keepclass android.support.** {*;}
#------------------------------------------------------------------------------------------------
-keepclasseswithmembernamesclass * {
native ;
}
-keepclasseswithmembersclass * {
public (android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembersclass * {
public (android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembersclass * extends android.app.Activity {
public void *(android.view.View);
}
#---------------------------------------------------------------------------------------------------
-keepclassmembersenum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keeppublic class * extends android.view.View{
*** get*();
void set*(***);
public (android.content.Context);
public (android.content.Context, android.util.AttributeSet);
public (android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembersclass * {
public (android.content.Context, android.util.AttributeSet);
public (android.content.Context, android.util.AttributeSet, int);
}
-keepclass * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembersclass * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keepclass **.R$* {
*;
}
-keepclassmembersclass * {
void *(**On*Event);
}
#---------------------------------webview------------------------------------
-keepclassmembersclass fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembersclass * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembersclass * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
-keep 顾名思义 :保留不需要混淆。除了基本指令区的需要保留,我们还有定制指令区需要保留,例如:实体类,json,webview,反射相关的类和方法,第三方jar文件,与js互相调用的类等。
//实体类 :保留该包下的类及子类,子类中方法等不混淆
-keep public class 包名.**{*;}
#zxing-第三方jar包 保留不被混淆
-libraryjars libs/zxing.jar
-dontwarn com.google.zxing.**
-keep class com.google.zxing.**{*;}
//so文件保留不被混淆,一般编译过程中会自动忽略混淆。不需要单独列出。
-libraryjars libs/x86/liblocSDK7a.so
注意:在此过程中尽可能剔除所有不需要混淆的类,避免不必要的闪退。
运行时注意在release模式下,进行正常的签名打包。代码混淆过之后可以反编译一下,看看效果,如果暴露了一些重要信息,可再次修改proguard-rules.pro文件混淆重要信息。
对于第三方jar,或者依赖的项目,不需要混淆,通用写法参考如下:
-dontwarn 主要是避免警告,-keep 主要是保留不被混淆
混淆后崩溃调试
通常情况下,写好混淆代码以后安装release版本的apk到手机有时根本运行不起来,有时启动了瞬间崩溃,其实原因很简单我们应用的库或者第三方jar被混淆了导致无法正常调用,那怎么查找是哪些不该混淆了的被混淆了?
在Android studio 中生成release包的同时 build\outputs\mapping\release文件夹下也生成了4个文件
分别有以下文件:
+ dump.txt 描述apk文件中所有类文件间的内部结构。
+ mapping.txt 列出了原始的类,方法,和字段名与混淆后代码之间的映射。
+ seeds.txt 列出了未被混淆的类和成员
+ usage.txt 列出了从apk中删除的代码
一般情况下,我们直接看mapping 和seeds这2个文件夹就可以了,在我调试过程中,使用notedpad打开mapping文件,将近12万行,怪不得用txt打开瞬间崩溃卡死。
mapping文件记录了所有的混淆前后的映射关系,告诉你混淆前后,据我分析,混淆后文件会变成abc,有些库你可能忽略,但是mapping会记录所有,你会发现 不改映射的第三方包 也变成abc了,这里就是出错的根本,不该混淆的被混淆了,一一找出,在proguard-rules.pro中-dontwarn 包名,-keep class 包名 就可以顺利解决。12万行并不是让你一行一行去看的,点击对应包的关键字滚动查看。一一过滤。
seeds文件夹是帮你查看是否需要混淆的类没有被混淆,当然都是可以修改的,查看过程中会有errormessage,搜索一下,没有最好,有了解决掉,也许不应该混淆的被你混淆掉。
混淆过后,app顺利运行,这种感觉超爽。
最终检验
反编译一下看看是否成功混淆?
我们需要工具dex2jar-2.0, jd-gui-windows-1.4.0,还需要一个apk 文件,dapp.apk,首先重命名为dapp.zip,解压缩得到dapp文件夹,得到dapp文件下的dex文件,将dex文件复制到dex2jar-2.0文件夹下。按下shift键的同时点击鼠标右键 打开命令窗口 ,在命令窗口中输入d2j-dex2jar.bat classes.dex 命令即可生成classes-dex2jar.jar文件。用jd-jui打开即可查看混淆后的代码。
至此就大功告成了,简单的代码混淆和反编译真的没有想象中那么难!