Android APK 加固–开发者角度(零)
1.APK加固介绍
Android应用程序使用的开发语言是Java语言,由于蚕蛹的是这种解释性语言,所以代码可以被反编译。如果没有经过混淆或是加密,会非常让人容易读懂,换言之就是容易被破解。为了能够使我们的apk更好的保护起来,Android的开发以及安全人员对APK进行了一系列的加固措施。对APK加固从开发者的角度来看,以下是常用的手段和方式:
1.使用proguard对apk中的源码进行混淆
2.对apk反编译之后的smali进行混淆
3.对apk中的字符串进行加密
4.对apk中的文件进行校验
2.使用proguard对apk中的源码进行混淆
AndroidStudio默认已经有proguard的支持,在小牧目录安排app下的build.gradle中有配置信息,只需要将false变为true即可。
默认只有release的配置,而工程默认是debug的,所以想要为debug上添加混淆要在release后面添加debug模式。
Proguard的混淆结果会处处到/build/outputs/mapping/release/目录或是/build/outputs/mapping/debug/,一共有四个输出文件:
1.dump.txt:描述APK中所有类文件的内部结构
2.mapping.txt:提供混淆前后类名,方法名和成员变量的对应关系。
3.seeds.txt:列出没有被混淆的类和成员
4.usage.txt:列出从apk中移除的代码
混淆之后可能出错,出错异常的分析:
http://simplyadvanced.net/blog/android-how-to-decode-proguards-obfuscated-code-from-stack-trace/
Proguard 包括四个功能,shrinker(压缩),optimizer(优化),obfuscator(混淆),preverifier(预校验)
3.对反编译之后的smali进行混淆
源码经过混淆之后,在一个产品级的apk中对apk的保护是有一定帮助的,而对于一些有一定经验的破解者,他们还是会找到比较关键的地方,一般这种方式都是经过反编译之后进行分析的,那么如果我们效仿windows下的保护软件,对smali代码进行混淆,那么某种程度上加大了破解者的分析难度。比较方便的混淆方式就是代码乱序。
1.代码乱序
原理:
我们在分析smali代码时,一般会借助于反编译工具反编译成java代码,代码乱序的主要目的就是想要将smali代码乱序之后,使反编译的效果大打折扣,这样就会挡住很短菜鸟,至少可以减慢分析速度。
2.对apk中的字符串进行加密
在开发过程中字符串的使用不可避免,但是这些字符串极可能是破解的关键点,比如服务器的地址和错误提示这些敏感的字符串信息。如果这些字符串采用硬编码的方式,很容易通过静态分析获取。
1.采用StringBuilder拼接的方式。
2.编码混淆:在硬编码的时候将字符串先转换成16进制的数组或者Unicode编码,在使用的时候在回转字符串,这种方式比StringBuider方式更难直接识别。
3.加密处理:先将字符串在本地进行加密处理,后将密文硬编码进去,运行时再进行解密。可以将解密方法通过JNI实现。
4.对apk中的文件进行校验
在apk中包括代码和资源以及签名文件,一般我们会对可执行文件进行校验,还有证书的签名进行校验,以及apk本身的校验。
1. 对apk中的dex文件进行校验
classes.dex是android虚拟机的可执行文件,我们所写的java代码骑士都在这里面,所有很多赌赢程序的篡改都是针对classes.dex文件的。
1.编写代码
代码比较简单,这里是通过计算好的crc保存在string.xml文件里,当然我们可以随便那一个值代替,等开发完成后替换掉。
private void verifyDex(){
Long dexCrc = Long.parseLong(this.getString(R.string.crc_value));
String apkPath = this.getPackageCodePath();
try{
ZipFile zipFile = new ZipFile(apkPath);
ZipEntry dexEntry = zipFile.getEntry("classes.dex");
//计算classex.dex的crc
long dexEntryCrc = dexEntry.getCrc();
Log.d("DEX",dexEntryCrc+" ");
//对比
if(dexCrc == dexEntryCrc){
Log.d("DEX","dex hasn't been modified");
}else{
Log.d("DEX","dex has ben modified");
}
}catch (IOException e){
e.printStackTrace();
}
}
2.对apk中的apk进行校验
与DEX校验不同apk校验必须把计算好的Hash值放在网络服务端,因为对APK的任何改动都会印象到最后的Hash值。
校验代码:
private void verifyApk(){
//获取data/app/**/base.apk 路径
String apkPath = getPackageResourcePath();
MessageDigest msgDigest;
try{
//获取apk并计算MD5
msgDigest = MessageDigest.getInstance("MD5");
byte[] bytes = new byte[4096];
int count;
FileInputStream fis;
fis = new FileInputStream(new File(apkPath));
while((count = fis.read(bytes))>0){
msgDigest.update(bytes,0,count);
}
//计算出MD5值
BigInteger bInt = new BigInteger(1,msgDigest.digest);
String md5 = bInt.toString(16;
fis.close();
Log.d("apk",md5);
//获取服务端的md5对比
}catch(){
}
3.对apk中的签名进行校验
每个APK都会经过开发者独有的整数进行签名,如果破解者对APK进行二次打包一般会用自己的签名证书 打包。这时候我们就可以通过校验签名证书的MD5值进行校验。
校验代码:
public void verifySignature(){
String packageName = this.getPackageName();
PackageManager pm = this.getPackageManager();
PackageInfo pi;
String md5 = "";
try{
pi = pm.getPackageInfo(packageName,PackageManager.GET_SIGNATURES);
Signature[] s=pi.signatures;
//计算md5值
//获取服务端签名证书,对比