代码混淆
ProGuard是ADT自带的apk混淆器,它的用途有:
1、压缩apk包的大小,能删除无用的代码,并简化部分类名和方法名。
2、加大破解源码的难度,因为部分类名和方法名被重命名,使得程序逻辑变得难以理解。
代码混淆的规则在proguard-project.txt中编写,然后在project.properties补充规则文件的路径,如下所示:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
下面是proguard-project.txt的一个例子:
#指定代码的压缩级别
-optimizationpasses 5
#是否使用大小写混合
-dontusemixedcaseclassnames
#优化/不优化输入的类文件
-dontoptimize
#是否混淆第三方jar包
-dontskipnonpubliclibraryclasses
#混淆时是否做预校验
-dontpreverify
#混淆时是否记录日志
-verbose
#混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保护注解
-keepattributes *Annotation*
#保持JNI用到的native方法不被混淆
-keepclasseswithmembers class * {
native <methods>;
}
#保持自定义控件的构造函数不被混淆,因为自定义控件很可能直接写在布局文件中
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
#保持自定义控件的构造函数不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保持布局中onClick属性指定的方法不被混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保持枚举enum类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保持序列化的Parcelable不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#指定哪些第三方jar包需要混淆
#-libraryjars libs/bcprov-jdk16-1.46.jar
#保持哪些系统组件类不被混淆
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.support.v4.**
-keep public class com.android.vending.licensing.ILicensingService
#保持哪些第三方jar包不被混淆
-keep class org.bouncycastle.**
-dontwarn org.bouncycastle.**
下面是进行代码混淆时的注意事项:
1、jni的方法要屏蔽混淆,因为so库要求包名、类名、函数名要完全一致
2、可能会在布局文件中直接引用的类名或方法名,要屏蔽混淆。包括自定义控件、布局中onClick属性指定的方法等等。
3、保持第三方jar包不被混淆,有时需要把“keep class”提到“dontwarn”前面。
4、jar包的文件名中不要有特殊字符,比如说“(”、“)”等字符在混淆时就会报错,文件名最好只包含字母、横线、小数点。
防二次打包
前面的《 Android开发笔记(七十)反编译初步》提到,apk破解得到smali文件后,可以进行修改并重新打包,从而制造一个山寨的APP。因此为了防止自己辛辛苦苦做的APP被别人山寨,就得在代码中加上防二次打包的处理。具体说来,首先开发者在打包前记下签名证书的MD码,然后在代码中获取app安装后的签名,对比两个签名的MD值是否一致,如果不一致就退出app,这样就能防止被二次打包了。
下面是获取apk签名的代码例子
public static String getSignMD5(Context context) {
String signMD5 = "";
String packageName = context.getPackageName();
PackageManager pkgMgr = context.getPackageManager();
PackageInfo info = null;
try {
info = pkgMgr.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
return signMD5;
}
if (info == null) {
return signMD5;
} else {
Signature[] signs = info.signatures;
if ((signs == null) || (signs.length == 0)) {
return signMD5;
} else {
Signature sign = signs[0];
signMD5 = MD5Util.encrypBytes(sign.toByteArray());
return signMD5;
}
}
}
以上代码用到了MD5加密,加密算法参见《 Android开发笔记(七十二)数据加密算法》。
下面是打包apk时的md5签名值的截图
下面是app运行时获取到的md5签名截图
花指令
代码混淆通过对类名和方法名重命名,只是加大了破解的难度,但并不能完全阻止代码被破解。有个办法就是通过让反编译程序出错,使得代码破解失败,花指令便是这样一种思想。花指令(junk code)意思是程序中加入一些与业务无关的指令,希望在反汇编的时候出错,让破解者无法正确地进行反汇编工作,从而迷失方向。常见的花指令常常是随意跳转,一旦目标位置是另一条指令的中间,反汇编的时候便会出现混乱。下面是花指令的一段示例代码(在jd-gui 0.3.6和1.4.0版本上都测试过,加了花指令的函数就无法正常破解):
//花指令开始
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("/proc/net/arp"));
String line;
while ((line = br.readLine()) != null) {
String[] splitted = line.split(" +");
if (splitted.length >= 0) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//花指令结束
当然,除了上面说的代码混淆、防止二次打包、花指令等等技巧,还有其他的一些技术手段,下面是其他几种代码加密方式:
1、把部分代码写入jni接口,因为so库难以反编译。例如在做签名校验时,原签名的值就可以保存在jni接口中。jni的介绍参见《 Android开发笔记(六十九)JNI实战》
2、把核心业务放到后端服务器上运行,app与服务器之前通过http接口通信。
3、使用第三方加密平台给app做加壳处理。
点此查看Android开发笔记的完整目录