大家都知道Android的APK是非常容易被破解的,目前市面上有太多的工具可以去反编译Apk,添加自己的代码,然后重新打包后发布到应用市场上。
因为Android应用市场五花八门,没有统一的监管机制,光靠应用市场去保护原创应用是完全行不通的,这就对我们的应用APK提出了较高的安全性要求。
当然破解和防止破解永远都是相对立的,双方技术都在不断的成长,所以我们不能完全杜绝破解,但是能在一定程度上避免破解,也就达到我们的目的了。
本文主要从以下几个方面简单介绍下Android应用程序的保护机制。
一、防止反编译
主要是保护DEX、RES、SO库等文件。
目前市面上也有第三方公司提供了Apk加固技术,比如360加固。经过加固后的APK能在一定程度上加大被反编译的难度。
二、防止二次打包
首先,我们需要阻止别的程序员反编译,动态调试我的应用。换句话说,就是需要阻止黑客程序员安装我们的应用到模拟器上。
其次,检测应用程序的完整性,我们可以用jni技术校验应用程序的完整性,也可以利用数字签名的方式来检测应用程序的完整性。1、校验包名
判断当前应用的包名是否和官方的应用包名一致。
2、检验签名
我们知道当一个apk文件被反编译破解、修改完代码逻辑之后,要使用jarsigner工具来重新给apk签名,才能运行修改后的apk文件。一个应用程序的签名,
是识别一个开发者的唯一标识,如果一个应用程序被别人反编译,那么这个应用程序的签名肯定会改变。当发现程序的签名改变时,我们就强制退出APP
3、校验classes.dex
4、校验整个apk的完整性
完整代码如下
public class ApkDefender {
/**
* 检查APP的合法性
* @param ctx
* @return false:APP被修改
*/
public static boolean checkInvalidate(Context ctx) {
//一、判断线上版本是否运行在模拟器上
if (isEmulator()) {
return false;
}
//二、应用完整性校验(建议放在服务器端校验)
//1、包名校验
if (!"com.terry.securitydefender".equals(ctx.getPackageName())) {
return false;
}
//2、检测签名(最好放在服务器端校验)
String signature = getSignature(ctx);
if("8219089110d0c8c1972cd7e34e96a027".equals(signature)){
return false;
}
//3、校验classes.dex(每次发版,classes.dex的Crc都不一样,因此最好放到服务器端校验)
long dexCrc = getDexCrc(ctx);
if(dexCrc>0&&dexCrc!=4156059761L){
return false;
}
/**
4、校验整个apk是否被修改(每次发版,APK的SHA1值都不一样,因此最好放到服务器端校验)
*/
String sha1 = getSha1(ctx);
if(!TextUtils.isEmpty(sha1)&&!"bfa2cb2d9160806efaea6adb505e379ac1057a49".equals(sha1)){
return false;
}
return true;
}
/**
* 判断应用程序是否运行在模拟器上
* @return
*/
private static boolean isEmulator() {
//只要是在模拟器中,不管是什么版本的模拟器,在它的MODEL信息里就会带有关键字参数sdk
if (Build.MODEL.contains("sdk") || Build.MODEL.contains(Build.VERSION.SDK)) {
return true;
} else {
return false;
}
}
/**
* 获得应用程序的数字签名
* @return
*/
private static String getSignature(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
// 得到当前应用程序的签名
PackageInfo info = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_SIGNATURES);
String signature = info.signatures[0].toCharsString();//原始md5值,比较长
signature= getMD5(signature);
return signature;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取classes.dex的Crc值,判断classes.dex是否被修改
* @param ctx
* @return
*/
private static long getDexCrc(Context ctx) {
try {
String apkPath = getApkPath(ctx);
if(TextUtils.isEmpty(apkPath))return 0;
ZipFile zipfile = new ZipFile(apkPath);
ZipEntry dexentry = zipfile.getEntry("classes.dex");
return dexentry.getCrc();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 获取Apk的SHA1值
* @return
*/
private static String getSha1(Context ctx) {
String apkPath = getApkPath(ctx);
if(TextUtils.isEmpty(apkPath))return null;
FileInputStream fis = null;
try {
MessageDigest msgDigest = MessageDigest.getInstance("SHA-1");
byte[] bytes = new byte[1024];
int byteCount;
fis = new FileInputStream(new File(apkPath));
while ((byteCount = fis.read(bytes)) > 0) {
msgDigest.update(bytes, 0, byteCount);
}
byte[] digest = msgDigest.digest();
String sha = HEX.byteToString(digest);
return sha;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fis) try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 获取apk的安装路径
* @param context
* @return
*/
private static String getApkPath(Context context) {
try {
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
ApplicationInfo appInfo = pkgInfo.applicationInfo;
return appInfo.sourceDir;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//----------------------------------------------
public static String getMD5(String str) {
return getMD5(str.getBytes());
}
public static String getMD5(byte[] toencode) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(toencode);
return HEX.byteToString(md5.digest());
} catch (NoSuchAlgorithmException e) {
}
return "";
}
public static String byteToString(byte[] b) {
char[] newChar = new char[b.length * 2];
for (int i = 0; i < b.length; i++) {
newChar[2 * i] = hex[(b[i] & 0xf0) >> 4];
newChar[2 * i + 1] = hex[b[i] & 0xf];
}
return new String(newChar);
}
private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
}
这样能在一定程度上加大二次打包的难度。因为这些都是代码编写的,破解者只要找到对应代码修改后,还是能二次打包。
三、代码安全
可以通过一些自动化检测平台来检测我们的应用是否存在漏洞,然后针对性的加强安全性。
腾讯的金刚审计系统 http://service.security.tencent.com/kingkong
360的捉虫猎手 http://appscan.360.cn/
阿里巴巴的聚安全 http://jaq.alibaba.com/gc/appsec/index.htm
百度的移动云测试中心 http://mtc.baidu.com/
梆梆加固测试平台 http://dev.bangcle.com/apps/index
参考:
http://www.freebuf.com/articles/terminal/102396.html
http://www.droidsec.cn/ 安卓安全中文站
http://blog.nsfocus.net/mobile-app-security-security-test/移动app检测要点
http://zbc.baijia.baidu.com/article/365622 10大移动安全威胁