Android APP安全策略

        在移动互联网时代,APP破解已经成为产业链。APP主要有3大威胁,盗版数据篡改山寨。盗版是以反编译为前提通过修改某些资源文件或者是代码文件,之后重新打包二次分发。数据篡改是通过人为地使用某些专业工具来修改正版APP内存中的数据,比如把一个收费的APP变成一个免费的APP。山寨主要是通过相似度来混淆用户。Android系统由于其开源的属性,市场上针对开源代码定制的ROM参差不齐,在系统层面的安全防范和易损性都不一样,Android应用市场对app的审核相对iOS来说也比较宽泛,为很多漏洞提供了可乘之机。因此,对于Android开发者来说,必须考虑APP的安全问题。本文旨在简单探讨一下APP常见的安全策略,为今后的开发提供参考。

 

一、代码混淆

        混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也可以有效缩减apk的体积。

(一)开启混淆

        ProGuard是一个混淆代码的开源项目,能够对字节码进行混淆、缩减体积、优化等处理。

        在Android Studio当中混淆APK,借助SDK中自带的Proguard工具,只需要修改build.gradle中的一行配置即可。如下所示:

release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

       其中minifyEnabled用于设置是否启用混淆,proguardFiles用于选定混淆配置文件。

(二)混淆规则

keep

 

保留类和类中的成员,防止被混淆或者移除

 

keep规则用于标识程序入口,被keep规则修饰的类及其成员会被指定为程序入口,从而免于被混淆。

keepnames

保留类和类中的成员,防止被混淆

 

前提是对应的成员在shrinking时没有被删减掉,即当成员没有被引用时会被移除。

keepclassmembers

只保留类中的成员,防止他们被混淆或者移除

 

类名会被混淆。

keepclassmembersnames

只保留类中的成员,防止他们被混淆

 

当类中的成员没有被引用时还是会被移除。

keepclasseswithmembers

保留类和类中的成员

 

前提是指明的类中必须含有该成员,没有的话还是会被混淆。

keepclasseswithmembersnames

保留类和类中的成员,前提是指明的类中必须含有该成员,没有的话还是会被混淆

 

需要注意的是没有被引用的成员会被移除。

        需要注意的是,如果仅仅指明要keep的类,而不指明其类成员:那ProGuard仅会保留其类和无参数构造方法不被删减或重命名。

-keep class package.example.Demo
//正确
-keep class package.example.Demo{*;}

        实际用例:

# plugin
-keep class com.xiaomi.plugin.** { *; }
-keep class com.xiaomi.miot.** { *; }
-keep class com.xiaomi.pluginbase.** { *; }
-keep class com.**.pojo.** { *; }
-keepclassmembers class * extends com.xiaomi.pluginhost.PluginHostActivity {
   private XmPluginBaseActivity mXmPluginActivity;
}
# Okhttp
-keep class okio.** { *; }
-keep class okhttp3.** { *; }
-dontwarn okio.**
-dontwarn okhttp3.**
-dontwarn org.apache.**

 

二、应用签名

        应用APK其实是一种特殊的Zip压缩包,所以也就无法避免恶意破解者解压 / 反编译修改内容,针对这个问题官方引入了应用签名机制

(一)消息摘要

    1. 什么是消息摘要?

        对一份数据,进行一个单向的 Hash 函数,生成一个固定长度的 Hash 值,这个值就是这份数据的摘要,也称为指纹。

    2. 摘要算法

        常见的摘要算法有 MD5SHA-1SHA-256 等。他们都有这些特点

  • 对于同一个摘要算法,无论输入的数据是什么,输出都是相同长度的值
    例如 MD5,无论数据有多大,输出总是128位的散列值。

  • 摘要算法是单向的,只能根据原始数据计算出它的摘要值,但是不能根据摘要值反算出原始数据。

  • 越优秀的摘要算法越难找到Hash碰撞。
    虽然长内容生成短摘要是必定会产生碰撞的,但一个优秀的摘要算法很难主动构造出两条数据,使得他们的摘要值相同。

  • 消息摘要的用途:可以用于校验数据的完整性。
    例如我们在下载文件时,数据源会提供一个文件的MD5。文件下载好之后,我们本地计算出文件的MD5,和数据源提供的MD5做对比,如果相同则文件是完整的。
    但独立使用消息摘要时,无法确保数据没有被篡改,因为无法保证从数据源获取的MD5有没有被中途篡改。

  • 相比加密算法,摘要算法速度都相对较快。

(二)数字签名

    1. 对称加密

        对称加密指的是 加密和解密使用相同密钥 的加密算法,这一类算法虽然在安全性上比不上非对称加密,却在加密 / 解密速度上占优。常见的非对称加密是 AES 、DES 加密。

    2. 非对称加密

        公开密钥密码体系:基于大整数的因数分解可以生成一对公钥和私钥。公钥和私钥是一一对应关系,一把私钥有着和它唯一对应的公钥,反之亦然。用公钥加密的数据,只能用和它对应的私钥解密,用私钥加密也只能同与之对应的公钥解密。

        根据公开密钥密码体系,我们有了非对称加密。非对称加密指的是 加密和解密使用是不同密钥 的加密算法。常见的非对称加密是 RSA 加密。

        如果用「公钥」对数据加密,用「私钥」去解密,这是「加密」;
        反之用「私钥」对数据加密,用「公钥」去解密,这是「签名」。

        简单地看,似乎没有区别,只是换了个名字。但实际上,两者的用途完全不一样。
        由于所有人都持有公钥,所以「签名」并不能保证数据的安全性,因为所有人都可以用公钥去解密。
        但「签名」却能用于保证消息的准确性和不可否认性。因为公钥和私钥是一一对应的,所以当一个公钥能解密某个密文时,说明这个密文一定来自于私钥持有者

    3. 签名及验证过程

        具体签名和验证的过程

  • 消息发送者持有 私钥 和 加密算法,称为信源;信源用私钥和加密算法对明文数据进行加密,得到密文数据,称为签体;
  • 接着把明文数据和密文数据同时给到消息接收者;
  • 消息接收者收到后,先取出密文数据,用公钥对密文解密,得到一份明文数据;
  • 再将这份明文数据和收到的明文数据做对比,如果相同则数据完整且可信。

        应用签名正是数字签名算法的应用场景之一,与其他应用场景类似,目的主要有两个方面:

     (1)认证

        Android 平台上运行的每个应用都必须有开发者的签名。在安装应用时,软件包管理器会验证 APK 是否已经过适当签名,安装程序会拒绝没有获得签名就尝试安装的应用。

     (2)验证完整性

        软件包管理器在安装应用前会验证应用摘要,如果破解者修改了 apk 里的内容,那么摘要就不再匹配,验证失败。

(三)Android签名方案

        目前Android 支持以下三种应用签名方案:

  • v1 签名方案:基于 Jar 签名;
  • v2 签名方案:提高验证速度和覆盖度(在 Android 7.0  Nougat 中引入);
  • v3 签名方案:实现密钥轮转(在 Android 9.0 Pie 中引入)。

        实际应用中,为了提高兼容性,建议按照 v1、v2、v3 的先后顺序采用签名方案,低版本平台会忽略高版本的签名方案在 APK 中添加的额外数据。 

    1. v1 签名方案

        v1 签名后会增加 META-INFO 文件夹,其中会有如下三个文件:

META-INF
├── MANIFEST.MF
├── CERT.SF
├── CERT.RSA

文件

描述

MANIFEST.MF

记录「apk 中每一个文件对应的摘要」(除了 META-INF 文件夹),防止某个文件被篡改

CERT.SF

记录「MANIFEST.MF 文件的摘要」和「MANIFEST.MF 中每个数据块的摘要」,防止 MANIFEST.MF 被篡改。

CERT.RSA

包含了「*.SF 文件的签名」和「包含公钥的开发者证书」

        签名流程:

  • 计算每个文件的 SHA-1 摘要,进行 BASE64 编码后写入 MANIFEST.MF 文件;
  • 计算整个 MANIFEST.MF 文件的 SHA-1 摘要,进行 BASE64 编码后写入 *.SF 文件;
  • 计算 MANIFEST.MF 文件中每一块摘要的 SHA-1 摘要,进行 BASE64 编码后写入 *.SF 文件;
  • 计算整个 *.SF 文件的数字签名(先摘要再私钥加密);
  • 将数字签名和 X.509 开发者数字证书(公钥)写入 *.RSA 文件。

        存在的问题:

  • 完整性覆盖范围不足:Zip 文件中部分内容不在验证范围,例如 META-INF 文件夹;
  • 验证速度较差:验证程序必须解压所有压缩的条目,这需要花费更多时间和内存。

        为了解决这些问题,Android 7.0 中引入了 APK 签名方案 v2。

    2. v2 签名方案

        v2 签名方案是一种 全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,相对于 v1 签名方案验证速度更快,完整性覆盖范围更广。

        相对与 v1 签名方案,v2 签名方案不再以文件为单位计算摘要了,而是以 1 MB 为单位将文件拆分为多个连续的(chunk),每个分区的最后一个块可能会小于 1 MB。

        验证流程:

        Android 7.0 及以上在校验时,会先判断是否具有 V2 签名,如果有 V2 签名,会走 V2 签名的校验流程,不再验证V1签名了

    3. v3 签名方案

        V3 签名方案的签名块格式和V2完全一样,只是 V2 的签名块信息存放在 ID = 0x7109871a 的数据块中,而 V3 的签名信息存放在 ID = 0xf05368c0 的数据块中。

在这个新的数据块中,记录了旧的签名信息和新的签名信息,以密钥转轮的方案,做签名的替换和升级。

 

三、APP加固

        加固也叫加壳。是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。加壳的程序可以有效阻止对程序的反汇编分析,以达到它不可告人的目的。这种技术也常用来保护软件版权,防止被软件破解。

        从App的加固技术来看,主流分为dex加固so加固。目前来看保护dex文件更为重要,因为dex反编译后的Java代码可读性更强。

(一)Dex加固

        加固原理:

        Dex加固又可以分为两种,一种是Dex文件整体加固,另一种是拆分Dex加固。这里主要介绍一下主流的Dex文件整体加固。

        Dex文件整体加固原理:

        在该过程中涉及到三个对象,分别如下:

  • 源程序也就是我们的要加固的对象,这里面主要修改的是原apk文件中的classes.dex文件和AndroidManifest.xml文件。
  • 壳程序主要用于解密经过加密了的dex文件,并加载解密后的原dex文件,并正常启动原程序。
  • 加密程序主要是对原dex文件进行加密,加密算法可以是简单的异或操作、反转、rc4、des、rsa等加密算法。

        该加固过程可以分为如下4个阶段:

  • (1)加密阶段
  • (2)合成新的dex文件
  • (3)修改原apk文件并重打包签名
  • (4)运行壳程序加载原dex文件

    1. 加密阶段

        加密阶段主要是讲把原apk文件中提取出来的classes.dex文件通过加密程序进行加密。

    2. 合成新的dex文件

        这一阶段主要是将上一步生成的加密的dex文件和我们的壳dex文件合并,将加密的dex文件追加在壳dex文件后面,并在文件末尾追加加密dex文件的大小数值。

        在壳程序里面,有个重要的类:

        ProxyApplication类,该类继承Application类,也是应用程序最先运行的类。所以,我们就是在这个类里面,在原程序运行之前,进行一些解密dex文件和加载原dex文件的操作。

    3. 修改原apk文件并重打包签名

        在这一阶段,我们首先将apk解压,会看到如下图的6个文件和目录。其中,我们需要修改的只有2个文件,分别是classes.dex和AndroidManifest.xml文件,其他文件和文件加都不需要改动。

        首先,我们把解压后apk目录下原来的classes.dex文件替换成我们在上一步合成的新的classes.dex文件。然后,由于我们程序运行的时候,首先加载的其实是壳程序里的ProxyApplication类。所以,我们需要修改AndroidManifest.xml文件,指定application为ProxyApplication,这样才能正常找到识别ProxyApplication类并运行壳程序。

    4. 运行壳程序加载原dex文件

        Dalvik虚拟机会加载我们经过修改的新的classes.dex文件,并最先运行ProxyApplication类。在这个类里面,有2个关键的方法:attachBaseContext和onCreate方法。ProxyApplication显示运行attachBaseContext再运行onCreate方法。

        在attachBaseContext方法里,主要做两个工作:

  • 读取classes.dex文件末尾记录加密dex文件大小的数值,则加密dex文件在新classes.dex文件中的位置为:len(新classes.dex文件) – len(加密dex文件大小)。然后将加密的dex文件读取出来,解密并保存到资源目录下
  • 然后使用自定义的DexClassLoader加载解密后的原dex文件

        在onCreate方法中,主要做两个工作:

  • 通过反射修改ActivityThread类,并将Application指向原dex文件中的Application
  • 创建原Application对象,并调用原Application的onCreate方法启动原程序

(二)App加固的利弊

    优势:

  • 保护自己核心代码算法,提高破解/盗版/二次打包的难度
  • 缓解代码注入/动态调试/内存注入攻击

    缺陷:

  • 影响兼容性
  • 影响程序运行效率.
  • 部分流氓、病毒也会使用加壳技术来保护自己
  • 部分应用市场会拒绝加壳后的应用上架

 

 

参考:《应用安全防护和逆向分析》

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值