由于Android系统固有的缺陷、Android应用分发渠道管理机制等问题,导致Android客户端程序很容易被反编译篡改/二次打包,经任意签名后可在各个渠道或论坛中发布,这不仅损害了开发者的知识产权,更可能威胁到用户的敏感信息及财产安全,因此客户端程序自身的安全性尤为重要,本文以客户端完整校验为主题,提供几种Android客户端完整性校验的实现思路,供广大开发者参考。

思路1:对classes.dex文件完整性校验

       Android工程代码中的所有java代码经编译和优化最终生成Dalvik虚拟机可执行的DEX文件,DEX文件最终会打包在APK文件中,针对APK代码的篡改攻击就是针对该文件,如通常使用apktool反编译APK文件,修改smali代码。APK包中的DEX文件如下图所示:

Android客户端代码保护技术-完整性校验_第1张图片

       因此,我们可以设计编写校验代码,实现在应用程序启动时计算所程序安装包中的classes.dex文件的哈希值(可通过CRC32、MD5等摘要算法计算得到),然后与预先计算的dex文件的哈希值(该值可存储在代码或配置文件中、也可与服务器通信交互获取)进行比较,以此验证代码文件是否被篡改。

       通过检查classes.dex文件的CRC32摘要值来判断文件是否被篡改的java实现代码如下所示:

    /**     * 通过检查classes.dex文件的CRC32摘要值来判断文件是否被篡改     *     * @param orginalCRC 原始classes.dex文件的CRC值     */    public static void apkVerifyWithCRC(Context context, String orginalCRC) {        String apkPath = context.getPackageCodePath(); // 获取Apk包存储路径        try {            ZipFile zipFile = new ZipFile(apkPath);            ZipEntry dexEntry = zipFile.getEntry("classes.dex"); // 读取ZIP包中的classes.dex文件            String dexCRC = String.valueOf(dexEntry.getCrc()); // 得到classes.dex文件的CRC值            if (!dexCRC.equals(orginalCRC)) { // 将得到的CRC值与原始的CRC值进行比较校验                Process.killProcess(Process.myPid()); // 验证失败则退出程序            }        } catch (IOException e) {            e.printStackTrace();        }    }

思路2:对apk包做完整性校验

       如果对apk包进行篡改,必会影响apk包的完整性校验值,因此根据思路1,我们也可以对整个apk包做哈希校验。

       通过检查apk包的MD5摘要值来判断代码文件是否被篡改的java实现代码如下图所示:

    /**     * 通过检查apk包的MD5摘要值来判断代码文件是否被篡改     *     * @param orginalMD5 原始Apk包的MD5值     */    public static void apkVerifyWithMD5(Context context, String orginalMD5) {        String apkPath = context.getPackageCodePath(); // 获取Apk包存储路径        try {            MessageDigest dexDigest = MessageDigest.getInstance("MD5");            byte[] bytes = new byte[1024];            int byteCount;            FileInputStream fis = new FileInputStream(new File(apkPath)); // 读取apk文件            while ((byteCount = fis.read(bytes)) != -1) {                dexDigest.update(bytes, 0, byteCount);            }            BigInteger bigInteger = new BigInteger(1, dexDigest.digest()); // 计算apk文件的哈希值            String sha = bigInteger.toString(16);            fis.close();            if (!sha.equals(orginalMD5)) { // 将得到的哈希值与原始的哈希值进行比较校验                Process.killProcess(Process.myPid()); // 验证失败则退出程序            }        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }

思路3:对签名文件中classes.dex哈希值的校验

        Android工程代码经编译打包生成apk包后,开发者需要对其签名才能在安卓市场上发布供用户下载和安装。对apk包签名后,会在原apk包结构基础上加入META-INF文件目录。签名后的apk包文件目录如下图所示:

Android客户端代码保护技术-完整性校验_第2张图片

       META-INF文件目录下含有三个文件:MANIFEST.MF文件、ANDROIDD.SF文件、ANDROIDD.RSA文件,META_INF目录文件结构如下图所示:

Android客户端代码保护技术-完整性校验_第3张图片

       其中,MANIFEST.MF文件描述了在签名时,签名工具对apk包中各个文件摘要计算后的哈希值,并对哈希值做了Base64编码。MANIFEST.MF文件中描述的classes.dex文件的SHA-1哈希值如下图所示:

Android客户端代码保护技术-完整性校验_第4张图片

       一旦攻击者对APK中反编译并篡改代码,经二次打包签名后的classes.dex文件的SHA-1必定改变,因此,我们可以将该文件中的classes.dex文件的SHA-1哈希值保存起来作为校验对比值,应用程序启动时读取apk安装包中的MANIFEST.MF文件,解析出classes.dex的SHA-1哈希值,然后与原SHA-1哈希值进行比较,判断此APK包代码文件是否被篡改。

       通过检查签名文件classes.dex文件的哈希值来判断代码文件是否被篡改的java实现代码如下所示:

    /**     * 通过检查签名文件classes.dex文件的哈希值来判断代码文件是否被篡改     *     * @param orginalSHA 原始Apk包的SHA-1值     */    public static void apkVerifyWithSHA(Context context, String orginalSHA) {        String apkPath = context.getPackageCodePath(); // 获取Apk包存储路径        try {            MessageDigest dexDigest = MessageDigest.getInstance("SHA-1");            byte[] bytes = new byte[1024];            int byteCount;            FileInputStream fis = new FileInputStream(new File(apkPath)); // 读取apk文件            while ((byteCount = fis.read(bytes)) != -1) {                dexDigest.update(bytes, 0, byteCount);            }            BigInteger bigInteger = new BigInteger(1, dexDigest.digest()); // 计算apk文件的哈希值            String sha = bigInteger.toString(16);            fis.close();            if (!sha.equals(orginalSHA)) { // 将得到的哈希值与原始的哈希值进行比较校验                Process.killProcess(Process.myPid()); // 验证失败则退出程序            }        } catch (NoSuchAlgorithmException e) {            e.printStackTrace();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }

       以上三种完整性校验实现思路的实现代码样例采用Java语言实现,从安全角度来讲,很容易通过反编译篡改patch掉,因此在实现完整性校验代码时还需参考以下几点建议:

       1.预先计算的dex文件的哈希值、签名文件的classes.dex的SHA-1哈希值,应避免直接明文硬编码存储在代码或配置文件中,可对其采用非对称加密存储,或采取与服务端通信的方式获取。

       2.由于dex文件很容易通过dex2jar、apktool反编译后逆向分析和破解,因此该完整性校验功能可进一步使用C/C++代码进行编写实现。另外,进一步提高安全性,还可通过源码混淆,如:开源的obfuscator-llvm项目,或对.so动态库加壳,增加逆向分析和破解的难度。

更多相关文章

  1. 【Android软件】ES文件浏览器:Android资源管理器
  2. [置顶] Android本地文件管理器思路解析一一增删改查具体实现
  3. 代码混淆—android被反编译的两种解决方案
  4. AS下如何生成自定义的.jks签名文件, 以及如何生成数字签名
  5. 【Android自学笔记】对应资源文件夹中的图标尺寸
  6. Android 查看/data/data文件夹并取回文件
  7. Android中解析与创建XML文件

随机推荐

  1. Android(安卓)简易音乐动态相册(多种动画
  2. Android(安卓)创建XMl文件
  3. android-轻松监听来电和去电
  4. Ubuntu 12.04(64位)下载并编译 Android(安
  5. 开源直播系统源码Android中activity跳转
  6. Android在标准linux基础上对休眠唤醒的实
  7. android注解[Jake Wharton Butter Knife]
  8. Android(安卓)Activity栈管理 制定关闭某
  9. Ubuntu下Android(安卓)APK反编译与重新编
  10. android 之 service