Android应用程序的保护机制

来源:互联网 发布:泉山区淘宝 编辑:程序博客网 时间:2024/05/21 14:57

大家都知道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大移动安全威胁

0 0