阅读 643

APK编译及安全防护

One 什么是APK

APK是AndroidPackage的缩写,即Android安装包(apk),可以通过将APK文件直接传到Android模拟器或手机中执行即可安装。APK的本质是一个zip的压缩包,用压缩软件打开后就可以看到里面的文件以及结构。

APK的文件组成:

  • AndroidManifest.xml:清单文件,主要包含四大组件注册信息,应用权限,和元数据等信息,此文件列出了应用的名称、版本、访问权限和引用的库文件。

  • classes.dex:Dalvid虚拟机上运行的字节码文件,我们在将java代码编译成class字节码文件后,通过android SDK提供的工具将class转换成dex字节码,dex文件可能有多个。

  • META—INF: 包含 CERT.SF 和 CERT.RSA 签名文件,以及 MANIFEST.MF 清单文件。CERT.SF、MANIFEST.MF是对资源做的SHA1 hash处理,CERT.RSA包含有公钥证书和签名。

  • res:包含未编译到 resources.arsc 中的资源例如:布局xml、图片、动画等。

  • resources.arsc 打包工具会提取此 XML 内容,将其编译为二进制文件形式,并将相应内容进行归档。此内容包括语言字符串和样式,以及未直接包含在 resources.arsc 文件中的内容(例如布局文件和图片)的路径。

  • lib/:包含特定于处理器软件层的编译代码。此目录包含每种平台类型的子目录,如 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 和 mips

Two APK编译

2.1 编译流程

由上图可知,APK打包的流程主要有如下几个步骤:

  1. 打包资源文件,生成R.java文件。
  2. 处理aidl文件,生成对应的java文件。
  3. 工程源码编译,生成.class文件,把.class文件编译为.dex文件。
  4. 代码文件和资源文件打包生成APK。
  5. 对apk文件进行签名。
  6. zip对齐,apk中的每个文件进行4个字节对齐操作,优化启动相应速度。

2.1.1. 打包资源文件,生成R.java文件

  • 工具aapt

  • 输入

    • res
    • assets
    • AndroidManifest.xml
    • Android基础库(Android.jar)文件
  • 过程

    • 编译res和assets目录下的资源并生成resources.arsc文件。
    • 调用ResourceTable.cpp文件中的parseAndAddEntry方法生成R.java文件。
    • 对res目录下的xml文件进行编译,这样处理过的xml文件就被简单“加密”了。
  • 输出

    • resources.arsc文件
    • R.java文件
  • 总结

    • assets和res/raw资源被原封不动地打包进APK,其它的资源都会被编译或者处理,除了assets资源之外,其他的资源都会被赋予一个资源ID。利用该特点,在apk加固时,可以把加密后的dex、so文件放到该目录。
    • R.java是java层面寻找资源的id表,resources.arsc是程序运行时用到的资源表。
    • APK运行时会根据设备的不同属性(如屏幕密度)寻址,resource.arsc就是通过相同的ID,根据不同的配置索引找到最佳资源。

2.1.2. 处理aidl文件,生成对应的java文件

这个主要是在aidl接口文件,通过SDK提供的AIDL工具生成相应的java文件,供程序调用,没有用到aidl的工程,并不会有这个过程

2.1.3. 工程源码编译

  • 工具

    • javac: 把java文件编译为.class文件的工具。
    • dx: 把.class文件打包为.dex文件的工具。
  • 输入

    • R.java
    • AIDL生成的java文件
    • 库jar文件,Android.jar
  • 输出

    • dex文件
  • 总结:

    • 这个过程主要是javac将java文件编译成class文件,再经dx工具转换成dex文件,压缩常量池,消除冗余信息等。

2.1.4. 打包生成APK

  • 工具

    • apkbuilder
  • 输入

    • 打包后的资源文件
    • 打包后的类文件,主要是dex文件
    • libs文件,包括so文件
  • 过程

    • 主要流程是先以resources.arsc文件为基础生成一个apk,然后调用ApkBuilderMain.java中的addSourceFolder方法添加工程资源,处理包括res和assets目录中的文件,添加完资源后调用addResourceFromJar方法往apk中写入依赖库,接着调用addNativeLibraries方法添加工程libs目录下的native库,最后调用sealApk关闭Apk文件。
  • 输出

    • 未签名的.apk文件

2.1.5. 对apk文件进行签名

  • android应用需要签名才能在设备上安装。

    • 开发时debug包会用系统自带的debug.keystore为debug包签名。
    • 正式发布时需要用公司或者项目的私钥对apk进行签名,这个私钥要妥善保管,一旦泄露,别人就可以任意修改包的内容然后重新打包签名。
  • 使用JDK自带签名工具的jarsigner:

    • jarsigner -keystore ./android.keystore -storepass password -signedjar ./signed.apk ./sign.apk android.keystore

2.1.6. zip对齐

对齐使用的是android-idk/tools目录下的zipalign工具,主要工作是将apk包中所有的资源文件起始偏移为4字节的整数倍,这样通过内存映射访问apk时的速度会更快。

2.2 编译细节流程

经过这几步的编译,输出的apk文件,最终是一个具有特定文件格式的静态文件。如果拿到该文件,分析文件格式和逆向,最终是可以分析一个破解apk的业务和流程,这就必然对apk产生安全问题。

2.3 Apk v1 & v2签名

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

  • v1 方案:基于 JAR 签名。
  • v2 方案:APK 签名方案 v2(在 Android 7.0 中引入)。
  • v3 方案:APK 签名方案 v3(在 Android 9 中引入)。

2.3.1. JAR 签名v1方案

从一开始,APK 签名就是Android 的一个有机部分。该方案基于签名的JAR。 但是v1方案存在以下一些不足:

  • 签名不保护APK的某些部分,例如ZIP元数据。

  • APK验证程序需要处理大量不可信(尚未经过验证)的数据结构,然后会舍弃不受签名保护的数据,这会导致相当大的受攻击面。

  • APK 验证程序必须解压所有已压缩的条目,而这需要花费更多时间和内存。

  • 由于这些不足导致apk签名后可以进行许多修改,可以移动甚至重新压缩文件,导致一些安全事件的发生。为了解决这些问题, google在Android 7.0以后引入了V2签名方案Android 7.0 中引入了APK 签名方案v2, Android 9之后引入v3(v2+)。

2.3.2. APK 签名方案v2 & v2+

APK v2签名方案是一种全文件签名方案,该方案能够:

  • 发现对APK的受保护部分进行的所有更改;
  • 有助于加快验证速度并增强完整性保证;

使用APK签名方案v2进行签名时,会在APK文件中插入一个APK签名分块,该分块位于“ZIP中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2签名和签名者身份信息会存储在APK签名方案v2分块中。

搭载Android 7.0 及更高版本的设备支持APK 签名方案v2(v2方案)及更高版本的方案(在Android P(Android 9) 中,v2方案已更新为v3方案,以便在签名分块中包含其他信息,但在其他方面保持相同的工作方式)。该方案会对APK的内容进行哈希处理和签名,然后将生成的“APK签名分块”插入到APK中。

在验证期间,v2+方案会将APK文件视为Blob,并对整个文件进行签名检查。对APK进行的任何修改(包括对ZIP元数据进行的修改)都会使APK签名作废。这种形式的APK验证不仅速度要快得多,而且能够发现更多种未经授权的修改。新的签名格式向后兼容,因此,使用这种新格式签名的APK 可在更低版本的Android设备上进行安装(会直接忽略添加到APK 的额外数据),但前提是这些APK 还带有v1签名。

验证程序会对照存储在“APK签名分块”中的v2+签名对APK的全文件哈希进行验证。该哈希涵盖除“APK签名分块”(其中包含v2+签名)之外的所有内容。在“APK签名分块”以外对APK进行的任何修改都会使APK的v2+签名作废。v2+签名被删除的APK也会被拒绝,因为v1签名指明相应APK带有v2签名,所以Android 7.0及更高版本会拒绝使用v1签名验证APK。

Three APK反编译

3.1 概述

APK反编译,就是把编译后的APK文件通过逆向工程手段获取到原始代码和资源的一个过程。由于APK文件是一个具有固定文件格式的静态文件,而且能够解析和执行的,所以理论上可以对一个APK文件进行逆向的分析进行反编译、破解。如果一个APK没有进行安全方面的防护措施,是可以通过很简单的几步给破解掉。

3.2 常用工具

apktool+dex2jar+JD-GUI

3.3 步骤

  • 拿到app.apk
  • 反编译class.dex文件 修改app.apk文件后缀为app.zip,解压app.zip文件

得到class.dex文件,使用dex2jar工具反编译为jar文件。

使用以下命令:./dex2jar.sh classes.dex

通过JD-GUI打开classes_dex2jar.jar可以看到大部分源。

通过apktool直接反编译app.apk即可看到资源文件 ./apktool d app.apk -f -o ./app3

反编译后AndroidMenifest.xml:

反编译后layout:

反编译后color文件:

至此,一个apk中所有的资源文件、代码文件全部被反编译出来,所以作为一个app开发者,为了数据、业务的安全,必须要对app进行安全处理,防止对用户造成信息、财产方面的损失,以下是几个对apk进行安全加固的处理方法。

Four APK安全加固

对于apk安全加固主要分为两个方面的安全防护:

  • 代码混淆
  • 签名对比
  • apk加壳
  • 反调试、反检测等技术
  • 其他

4.1 代码混淆

混淆代码并不是让代码无法被反编译,而是将代码中的类、方法、变量等信息进行重命名,把它们改成一些毫无意义的名字。因为对于我们而言可能CellPhone类的call()方法意味着很多信息,而A类的a()方法则没有任何意义,但是对于计算机而言,它们都是平等的,计算机不会试图去理解CellPhone是什么意思,它只会按照设定好的逻辑来去执行这些代码。所以说混淆代码可以在不影响程序正常运行的前提下让破解者对代码逻辑的理解难度加大,从而大大提升了程序的安全性。

4.2 签名对比

  • 原理

APK必须被签名才能够安装到手机中,没有被签名的apk是无法安装到设备中的。但是签名在反编译之后是获取不到的,所以只能用自己的签名文件去签名,但是在已经安装了应用设备中在安装一个签名不一致的应用会导致安装失败。

  • 应用

利用该原理,可以在程序启动的时候获取应用签名,然后和正确的签名值做比对,如果不符合就直接退出程序。

  • 破解方法

既然是在程序的入口进行签名比对,就可以反编译找到程序入口处相关的代码,进行处理删除或者把签名信息改成自己的签名值,重新打包即可解决这个问题。

4.3 dex文件加固

  • 加固原理

- 加密的源apk

- 自己的壳apk,负责解密apk,并动态加载apk的工作

- 加密工具,负责将源apk进行加密和壳dex合并成新的dex
复制代码

4.4 反调试检测(ptrace)

反调试检测是为了应对现在很多破解者使用IDA进行动态方式调试so文件,从而获取重要的信息,知道IDA进行so动态调试是基于进程的注入技术,然后使用Linux中的ptrace机制,进行调试目标进程的附加操作。ptrace机制有一个特点,如果一个进程被调试了,在它进程的status文件中有一个字段TracerPid会记录调试者的进程id值,如图所示:

查看文件:/proc/[myPid]/status,在第六行,有一个TracerPid字段,就是记录了调试者的进程id。那么就可以这么做来达到反调试的功效了:轮询遍历自己进程的status文件,然后读取TracerPid字段值,如果发现它大于0,就代表着自己的应用在被人调试,所以就立马退出程序。原理知道了,代码实现也很简单,这里用pthread创建一个线程,然后进行轮询操作: 使用pthreadcreate创建一个线程,线程启动之后执行threadfunction函数:

看看thread_funcation函数:

4.5 其它

  • Dex-Java2C:将Java代码翻译为C代码,并实施Native层的代码混淆保护。

  • So文件加壳:对SO文件进行整体加壳保护,防止IDA Pro等工具逆向分析。

  • 内存加密:防止内存数据被篡改或Dump,比如Dump解密后的Java代码。

  • 自身虚拟化保护:专业版采用代码虚拟化技术对自身代码进行保护,防止逆向分析。

对于apk安全方面的技术,层出不穷,安全攻防拉锯技术不断改进不断提升。

Five 结论

apk的编译是把我们写的代码、资源、库文件打包成可以在Android设备上运行的过程,而为了代码、数据、业务安全诉求需要对apk进行安全防护工作。

安全攻防是魔高一尺、道高一丈,特别是金融类app需要高度重视的工作,在此与大家一起学习和分享。

Thanks!

作者简介

焦亚克, 民生科技有限公司用户体验技术部开发工程师。