由于Android系统固有的缺陷、Android应用分发渠道管理机制等问题,导致Android客户端程序很容易被反编译篡改/二次打包,经任意签名后可在各个渠道或论坛中发布,这不仅损害了开发者的知识产权,更可能威胁到用户的敏感信息及财产安全,因此客户端程序自身的安全性尤为重要,本文以客户端完整校验为主题,提供几种Android客户端完整性校验的实现思路,供广大开发者参考。
思路1:对classes.dex文件完整性校验
Android工程代码中的所有java代码经编译和优化最终生成Dalvik虚拟机可执行的DEX文件,DEX文件最终会打包在APK文件中,针对APK代码的篡改攻击就是针对该文件,如通常使用apktool反编译APK文件,修改smali代码。APK包中的DEX文件如下图所示:
![](https://img-blog.csdn.net/20160921115732376?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
因此,我们可以设计编写校验代码,实现在应用程序启动时计算所程序安装包中的classes.dex文件的哈希值(可通过CRC32、MD5等摘要算法计算得到),然后与预先计算的dex文件的哈希值(该值可存储在代码或配置文件中、也可与服务器通信交互获取)进行比较,以此验证代码文件是否被篡改。
通过检查classes.dex文件的CRC32摘要值来判断文件是否被篡改的java实现代码如下所示:
-
-
-
-
-
- public static void apkVerifyWithCRC(Context context, String orginalCRC) {
- String apkPath = context.getPackageCodePath();
- try {
- ZipFile zipFile = new ZipFile(apkPath);
- ZipEntry dexEntry = zipFile.getEntry("classes.dex");
- String dexCRC = String.valueOf(dexEntry.getCrc());
- if (!dexCRC.equals(orginalCRC)) {
- Process.killProcess(Process.myPid());
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
思路2:对apk包做完整性校验
如果对apk包进行篡改,必会影响apk包的完整性校验值,因此根据思路1,我们也可以对整个apk包做哈希校验。
通过检查apk包的MD5摘要值来判断代码文件是否被篡改的java实现代码如下图所示:
-
-
-
-
-
- public static void apkVerifyWithMD5(Context context, String orginalMD5) {
- String apkPath = context.getPackageCodePath();
- try {
- MessageDigest dexDigest = MessageDigest.getInstance("MD5");
- byte[] bytes = new byte[1024];
- int byteCount;
- FileInputStream fis = new FileInputStream(new File(apkPath));
- while ((byteCount = fis.read(bytes)) != -1) {
- dexDigest.update(bytes, 0, byteCount);
- }
- BigInteger bigInteger = new BigInteger(1, dexDigest.digest());
- 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包文件目录如下图所示:
![](https://img-blog.csdn.net/20160921115815695?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
META-INF文件目录下含有三个文件:MANIFEST.MF文件、ANDROIDD.SF文件、ANDROIDD.RSA文件,META_INF目录文件结构如下图所示:
![](https://img-blog.csdn.net/20160921115827789?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
其中,MANIFEST.MF文件描述了在签名时,签名工具对apk包中各个文件摘要计算后的哈希值,并对哈希值做了Base64编码。MANIFEST.MF文件中描述的classes.dex文件的SHA-1哈希值如下图所示:
![](https://img-blog.csdn.net/20160921115848774?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
一旦攻击者对APK中反编译并篡改代码,经二次打包签名后的classes.dex文件的SHA-1必定改变,因此,我们可以将该文件中的classes.dex文件的SHA-1哈希值保存起来作为校验对比值,应用程序启动时读取apk安装包中的MANIFEST.MF文件,解析出classes.dex的SHA-1哈希值,然后与原SHA-1哈希值进行比较,判断此APK包代码文件是否被篡改。
通过检查签名文件classes.dex文件的哈希值来判断代码文件是否被篡改的java实现代码如下所示:
-
-
-
-
-
- public static void apkVerifyWithSHA(Context context, String orginalSHA) {
- String apkPath = context.getPackageCodePath();
- try {
- MessageDigest dexDigest = MessageDigest.getInstance("SHA-1");
- byte[] bytes = new byte[1024];
- int byteCount;
- FileInputStream fis = new FileInputStream(new File(apkPath));
- while ((byteCount = fis.read(bytes)) != -1) {
- dexDigest.update(bytes, 0, byteCount);
- }
- BigInteger bigInteger = new BigInteger(1, dexDigest.digest());
- 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动态库加壳,增加逆向分析和破解的难度