[翻译]OWASP 安卓测试指南(v1.2 - 14 May 2020)节选





原文链接:
平台概述https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview
Android 基础安全测试https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05b-basic-security_testing
Android 反逆向防御https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05j-testing-resiliency-against-reverse-engineering

目录

平台概述

本节从架构的角度介绍Android平台。 讨论了以下五个关键领域:

  1. Android安全架构
  2. Android应用程序结构
  3. 进程间通信(IPC)
  4. Android应用发布
  5. Android应用攻击面
    访问官方的 Android 开发者文档网站(https://developer.android.com/),可以获取更多有关 Android 平台的详细信息。

Android 安全架构

Android 是谷歌基于 Linux 开发的开源平台,它充当移动操作系统(OS)。如今,该平台是各种现代技术的基础,如手机、平板电脑、可穿戴技术、电视和其他"智能"设备。典型的 Android 版本具有一系列预装("内置")的应用程序,并支持通过 Google Play 商店和其他市场安装第三方应用。

Android 的软件栈由几个不同的层组成。每层都定义了接口并提供特定的服务。

Android 软件栈

在最低层,Android 是基于 Linux 内核的变种。 在内核之上,硬件抽象层(HAL)定义了用于与内置硬件组件进行交互的标准接口。几种HAL 的实现打包在了共享库模块中,Android 系统需要时就会调用。这是允许应用程序与设备的硬件进行交互的基础,例如,它允许一个内置的电话应用程序使用设备的麦克风和扬声器。

Android 应用程序通常使用 Java 编写并编译为 与传统的 Java 字节码有些不同的 Dalvik 字节码。首先将 Java 代码编译为 .class 文件,然后使用 dx 工具将 JVM 字节码转换为 Dalvik 的 .dex 格式,这样就创建了 Dalvik 字节码。

Java vs Dalvik

当前版本的安卓在 Android 运行时(ART)上执行此字节码。ART 是安卓原始运行时 Dalvik 虚拟机的后继者。 Dalvik 和 ART 之间的关键区别在于字节码的执行方式。

在Dalvik中,字节码在执行时转换为机器码,这一过程称为即时(JIT)编译。JIT编译会对性能产生不利的影响:每次执行应用程序时都必须进行编译。为了提高性能,ART 引入了提前(AOT)编译。顾名思义,应用会在首次执行之前进行预编译。预编译的机器代码会用于所有后续的执行。AOT 将性能提高了两倍,同时降低了功耗。

Android 应用程序不能直接访问硬件资源,并且每个应用程序都运行在自己的沙盒中。这允许对资源和应用程序进行精确的控制:例如,崩溃的应用程序不会影响设备上运行的其他应用程序。同时,Android 运行时会控制分配给应用程序的最大系统资源数量,防止任何一个应用程序垄断过多的资源。

安卓用户和组

尽管 Android 操作系统是基于 Linux,但它并不像其他类 Unix 系统那样实现用户帐户。在 Android 中 Linux 内核对应用沙盒的多用户支持:除了少数的例外,每个应用程序都像运行在一个单独的 Linux 用户下,有效地与其他应用和操作系统的剩余部分隔离。

文件 system/core/include/private/android_filesystem_config.hhttp://androidxref.com/7.1.1_r6/xref/system/core/include/private/android_filesystem_config.h)包含了一个系统进程分配的预定义用户和组的列表。安装其他应用程序时,会添上它们的 UID(userID)。更多详细信息,参阅 Bin Chen 关于 Android 沙箱的博客文章(https://pierrchen.blogspot.com/2016/09/an-walk-through-of-android-uidgid-based.html)。

例如,Android 7.0(API 等级24)定义了以下系统用户:

Android 设备加密

安卓从 Android 2.3.4(API 等级10)开始支持设备加密,此后发生了一些大的变化。Google 强制所有的设备运行 Android 6.0(API 等级23)或支持存储加密的更高版本,有些低端设备被豁免,因为这会严重影响性能。在下面各节中,你可以找到有关设备加密及其算法的信息。

全盘加密

Android 5.0(API 等级 21)及更高版本支持全盘加密。这种加密使用用户设备密码保护的单个密钥来加密和解密 userdata 分区。现在这种加密已被弃用,并认为应尽可能使用基于文件的加密。全盘加密具有一些缺点,例如,如果用户未输入解锁密码,则重启后将无法接听电话或没有操作警报。

基于文件加密

Android 7.0(API 等级24)支持基于文件的加密。基于文件的加密允许使用不同的密钥对不同的文件进行加密,以便可以独立解密它们。支持这种加密方式的设备也支持 Direct Boot 模式,Direct Boot 模式下设备可以在用户未解锁设备时访问警报或辅助服务等能。

Adiantum

AES 在大多数现代 Android 设备上用于存储加密。实际上,AES 已经成为一种广泛使用的算法,最新的处理器实现有专用的指令集来提供硬件加速加密与解密操作,例如带有 Cryptography Extensions 的 ARMv8 或具有AES-NI 扩展的 x86。但是,并非所有设备都能及时使用 AES 进行存储加密。尤其是运行Android Go 的低端设备。这些设备通常使用低端处理器,例如 ARM Cortex-A7,它们没有硬件加速的 AES。

Adiantum 是由 Google 的 Paul Crowley 和 Eric Biggers 设计的密码结构,用于填补那些不能以至少 50 MiB/s 速度运行 AES 的设备的空白。Adiantum 仅依赖于加法、移位旋转和异或,所有处理器都支持这些操作,与使用 AES 相比,低端处理器的加密速度是 AES 的4倍,解密速度是 AES 的5倍。

  • Adiantum 由其他密码组成:
  • NH:哈希函数。
  • Poly1305:消息认证代码(MAC)。
  • XChaCha12:一种流密码。
  • AES-256:AES的一次调用。

Adiantum是一种新的密码,但只要 ChaCha12 和 AES-256 被认为是安全的,它就是安全的。它的设计者没有创建任何新的加密基元,而是依赖于其他著名的、经过深入研究的基元来创建一个新性能的算法。

Adiantum 适用于 Android 9(API 等级 28)和更高版本。Linux 内核5.0及更高版本支持该功能,而内核4.19、4.14 和4.9需要修补。Android 不向应用程序开发人员提供使用 Adiantum 的API。这个加密将由 ROM 开发人员或设备供应商希望在不牺牲低端设备性能的情况下提供全磁盘加密时考虑并实施。在撰写本文时,尚无公共密码库实现此加密可在 Android 应用程序上使用。应该注意的是,AES 在具有 AES指令集的设备上运行得更快,在这种情况下强烈不建议使用 Adiantum。

Android 安全强化

Android 包含许多不同的功能,试图使恶意应用程序更难逃逸出沙盒。由于应用程序在您的设备上有效地运行代码,很重要的一点就是,即使应用程序本身不可信但也可以安全地执行。以下各节解释了哪些缓解措施可以防止应用程序滥用漏洞。需要注意的是,操作系统从来都不是 100% 安全的,即使采取了这些缓解措施,新的漏洞也仍会定期被发现。

SELinux

安全强化的 Linux(SELinux)使用强制访问控制(MAC)系统来进一步锁定哪些进程可以访问哪些资源。每个资源都有一个标签,其形式是 user:role:type:mls_level,它定义了哪些用户可以对其执行哪种类型的操作。例如,一个进程可能只能读取文件,而另一个进程可以编辑或删除该文件。这样,通过使用最小特权原则,易受攻击的进程更难通过权限提升或内网漫游被漏洞利用。

有关更多信息,请访问 Android 安全网站(https://source.android.com/security/selinux)。

ASLR, KASLR, PIE and DEP

自 Android 4.1(API 等级 15)以来,地址随机化(ASLR)就已成为 Android 的一部分,它是防止缓冲区溢出攻击的标准保护措施,这样可以确保将应用程序和操作系统都加载到随机的内存地址,从而很难获取特定的内存区域或库的正确地址。在 Android 8.0(API 等级26)中,也为内核实现了这种保护(KASLR)。仅当可以将应用程序加载到内存中的随机位置时才可以使用ASLR保护,这由应用程序的位置无关可执行文件(PIE)标志指示。从 Android 5.0(API 等级 21)开始,不再支持未启用 PIE 的原生库。最后,数据执行保护(DEP)可以防止代码在堆栈和堆上执行,这也可以用来防止缓冲区溢出漏洞。

有关更多信息,请访问 Android 开发者博客(https://android-developers.googleblog.com/2016/07/protecting-android-with-more-linux.html)。

SECCOMP

Android 应用程序可以包含用C或C ++编写的本机代码。这些编译的二进制文件可以通过 Java 本地接口(JNI)绑定与 Android 运行时通信,也可以通过系统调用与 OS 通信。某些系统调用没用被实现,或者不应由普通应用程序调用。由于这些系统调用直接与内核通信,因此它们是漏洞利用开发人员的主要目标。在 Android 8(API 等级26)上,Android 引入了对所有基于 Zygote 的进程(即用户应用程序)的 Secure Computing(SECCOMP)过滤器的支持, 这些过滤器将可用的系统调用限制为 bionic 公开的系统调用。

有关更多信息,请访问 Android 开发者博客(https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html)。


Android 上的应用程序

与操作系统通信

Android 应用程序通过与提供高级 Java API 的抽象层即 Android 框架层与系统服务进行交互,这些服务大多数是通过常规的 Java 方法来调用的,并转换为对在后台运行的系统服务的 IPC 调用。系统服务的实例有:

  • 连接性(Wi-Fi,蓝牙,NFC等)
  • 文件
  • 照相机
  • 地理位置(GPS)
  • 麦克风

框架层还提供了常见的安全功能,例如加密。

每个新的 Android 版本都会更改API规范。关键的漏洞修复和安全补丁通常也适用于早期的版本,在撰写本文时,支持的最旧的 Android 版本是 Android 8.1(API 等级27),当前的 Android 版本是Android 10(API 等级29)。

值得注意的API版本:

  • Android 4.2(API 等级16),2012年11月发布 (引入 SELinux)
  • Android 4.3(API 等级18),2013年7月发布(默认启用SELinux)
  • Android 4.4(API 等级19),2013年10月发布(引入了几个新的 API 和 ART)
  • Android 5.0(API 等级21),2014年11月发布(默认使用 ART,并添加了许多其他功能)
  • Android 6.0(API级别23),2015年10月发布(许多新功能和改进,包括在运行时授予详细的权限设置,而不是在安装期间授予全部权限或不提供任何权限)
  • Android 7.0(API 等级24-25),2016年8月发布(ART 上的新 JIT 编译器)
  • Android 8.0(API 等级26-27),2017年8月发布(安全性方面做了很多改进)
  • Android 9(API 等级28),2018年8月发布(限制麦克风或摄像头的后台使用,引入锁定模式,所有应用程序默认 HTTPS)
  • Android 10(API级别29),2019年9月发布(通知气泡,Project Mainline)

普通应用程序的Linux UID / GID

Android 利用 Linux 用户管理来隔离应用程序。但 这种方法不同于传统 Linux 环境下多个应用程序常由同一用户运行的用户管理用法。安卓为每个 Android 应用程序创建了一个唯一的 UID,并在单独的进程中运行该应用程序。因此,每个应用程序只能访问自己的资源,此保护由 Linux 内核强制执行。

通常,为应用程序分配的 UID 在10000和99999之间。Android 应用程序会根据他们的 UID 收到一个用户名。例如,UID 为10188的应用程序收到用户名u0_a188。 如果授予了应用程序请求的权限,则会将相应的组 ID 添加到该应用程序的进程中。例如,下面的应用程序的用户 ID 是10188,它属于组 ID 3003(inet),该组与 android.permission.INTERNET 权限相关。id 命令的输出如下所示。

组ID和权限之间的关系在下面的文件中定义:

frameworks/base/data/etc/platform.xml(http://androidxref.com/7.1.1_r6/xref/frameworks/base/data/etc/platform.xml

应用沙盒

应用程序在Android的应用沙盒中执行,应用沙盒可以将应用程序的数据和代码执行与设备上的其他应用程序隔离,这种隔离增加了一个安全层。

新应用程序安装后会创建一个根据该应用程序包命名的新目录,在以下路径:/data/data/[package-name]。此目录保存了应用程序的数据。Linux 目录权限会被设置,以便只能使用应用程序的唯一 UID 来读取和写入目录。

Sandbox

我们可以通过查看/data/data文件夹中的文件系统权限来确认这一点。例如,我们可以看到 Google Chrome 和 Calendar 都分配了一个目录,并在不同的用户帐户下运行:

希望自己的应用程序都使用一个共享沙盒的开发人员可以避开沙盒。当两个应用使用相同的证书进行签名并显式共享相同的用户 ID(在它们的 AndroidManifest.xml 文件中包含 sharedUserId)时,每个应用都可以访问对方的数据目录。请参阅以下示例,在 NFC 应用中实现此目的:

Zygote

Zygote 进程在 Android 初始化期间启动。 Zygote 是用于启动应用程序的系统服务。Zygote 进程是一个包含应用程序需要的所有核心库的“基本”进程,其中。启动后,Zygote 打开套接字 /dev/socket/zygote 并监听来自本地客户端的连接。接收到连接后,它将派生一个新进程,然后该进程将加载并执行应用专用代码。

App 生命周期

在 Android 中,应用程序进程的生存期由操作系统控制。当启动应用程序组件并且该应用程序尚未运行任何其他组件时,一个新的 Linux 进程会被创建。当不再需要进程或需要回收内存以运行更重要的应用程序时,Android 可能杀死该进程。 进程是否杀死主要和用户与进程交互的状态有关。通常,进程可能处于四种状态之一。

  • 前台进程(例如,在屏幕顶部运行的活动或正在运行的广播接收器)

  • 可见进程是用户意识到的进程,因此终止该过程将对用户体验产生明显的负面影响。一个例子是运行一个用户在屏幕上可见但在前台不可见的活动。

  • 服务进程是托管使用startService方法启动的服务的进程。尽管这些进程对用户来说不是直接看到的,但是它们通常是用户关心的事情(例如,后台网络数据的上传或下载),因此,除非没有足够的内存来保留所有前台和可见进程,系统将始终保持这些进程运行。

  • 缓存进程是当前不需要的进程,因此系统可以在需要内存杀死它。
    应用程序必须实现对多个事件做出反应的回调方法。例如,在首次创建应用进程时会调用onCreate处理程序,其他回调方法包括 onLowMemoryonTrimMemory 和 onConfigurationChanged

App Bundles

Android 应用程序能以两种形式发布:A
Android Package Kit(APK)文件或
Android App Bundle(.aab)。Android App Bundle 提供了应用程序需要的所有资源,但推迟了 APK 的生成及向 Google Play 的签名。
App Bundles 是经过签名的二进制文件,包含有应用程序的代码在几个模块中。基本模块包含应用程序的核心,可以通过各种包含应用程序的新的功能的模块进行扩展,这在 app bundle 开发者文档(https://developer.android.com/guide/app-bundle)中有进一步的说明。如果您有一个Android App Bundle,最好使用 Google 的bundletool(https://developer.android.com/studio/command-line/bundletool)命令行工具来构建未签名的APK,以便使用 APK 上的现有工具。你可以通过运行以下命令从 AAB 文件创建 APK:

如果你想创建已签名的 APK,准备部署到测试设备,请使用:

我们建议你同时测试带有和不带有附加模块的 APK,以便清楚附加模块是否引入和(或)修复了基本模块的安全问题。

Android Manifest

每个应用程序都有一个 Android Manifest 文件,该文件以二进制 XML 格式嵌入内容。这个文件的标准名称是 AndroidManifest.xml,它位于应用程序的 Android Package Kit(APK)文件的根目录中。

Manifest 文件描述了应用程序的结构、的组件(活动、服务、内容提供者和 intent 接收者)和请求的权限。它还包含通用的应用程序的元数据,比如应用程序的图标、版本号和主题。该文件可能会列出其他信息,比如兼容的 API(最小、目标和最大的SDK版本)和可以安装的存储类型(外部或内部)(https://developer.android.com/guide/topics/data/install-location.html)。

下面是一个清单文件的示例,包括包名(惯例是反向的URL,但任何字符串都可以接受)。它还列出了应用程序版本,相关 SDK,需要的权限,给出了内容提供者,使用 intent 过滤器的广播接收器和应用程序及其活动的描述:

可用清单选项的完整列表在官方 Android Manifest file 文档(https://developer.android.com/guide/topics/manifest/manifest-intro.html)中。

App 组件

Android 应用程序由几个高级组件组成。主要组成有:

  • 活动
  • 碎片
  • Intent
  • 广播接收器
  • 内容提供者和服务

所有这些元素都是由 Android 操作系统以 API 中预定义类的形式提供的。

活动

Activity 组成了应用程序的可见部分。每个页面都有一个活动,所以一个有三个不同页面的应用程序会实现三个不同的活动。活动通过继承 Activity 类来声明,包含所有用户界面元素:碎片、视图和布局。

每个活动都需要在 Android 清单中使用以下语法声明:

未在清单文件中声明的活动不会被显示,试图启动它们将引发异常。

和应用程序一样,活动也有自己的生命周期,并且需要监视系统的变化来处理它们。活动可以处于以下状态:活动、暂停、停止和不活动,这些状态由 Android 操作系统管理。对应的, 活动可以实现以下事件管理器:

  • onCreate
  • onSaveInstanceState
  • onStart
  • onResume
  • onRestoreInstanceState
  • onPause
  • onStop
  • onRestart
  • onDestroy

应用程序可以不显式地实现所有的事件管理器,在这种情况下会采取默认的操作。通常,至少onCreate管理器会被应用程序开发人者重写,这是大多数用户界面组件声明和初始化的方式。当资源(如网络连接或数据库连接)必须显式释放,或者当应用程序关闭时必须执行特定操作时,onDestroy可能被重写。

碎片

Fragment 表示活动中的一个行为或用户界面的一部分。Android 在 Honeycomb 3.0 版本 (API 等级11)中引入了碎片。

碎片用于封装界面的各个部分,以促进可重用性和适应不同屏幕大小。碎片是自治的实体,它们包含了所有需要的组件(它们有自己的布局、按钮等等)。然而,它们必须与活动集成才能有效:碎片不能单独存在。它们有自己的生命周期,与实现它们的活动的生命周期相关联。

因为碎片有自己的生命周期,所以碎片类包含可以重新定义和继承的事件管理器。这些事件管理器包括 onAttach、onCreate、onStart、onDestroy 和 onDetach。其他一些例子,读者应该参考 Android Fragment 规范(https://developer.android.com/guide/components/fragments)以获得更多细节。

通过继承 Android 提供的 Fragment 类,可以轻松实现碎片:

Java 示例:

Kotlin 示例:

碎片不需要在清单文件中声明,因为它们依赖于活动。

为了管理碎片,活动可以使用碎片管理器 (FragmentManager类)。这个类使得查找、添加、删除和替换相关的碎片变得很容易。

碎片管理器可以通过以下方式创建:

Java 示例:

Kotlin 示例:

碎片不一定有用户界面,它们可以成为管理和应用程序用户界面相关的后台操作的一种方便而高效的方式。一个片段可以被声明为持久的,这样即使活动被破坏,系统仍然保持它的状态不变。

进程间通信

我们已经了解到,每个Android进程都有自己的沙箱地址空间。进程间通信机制允许应用程序安全地交换信号和数据。AndroidIPC不依赖默认的Linux IPC工具,而是基于Binder,即OpenBinder的自定义实现。大多数Android系统服务和所有高级IPC服务都依赖Binder

Binder 这个词有很多不同的含义,包括:

  • Binder 驱动程序:内核级驱动程序

  • Binder 协议:用于与 Binder 驱动程序通信的基于 ioctl 的低级协议

  • IBinder 接口:Binder 对象实现的定义良好的行为

  • Binder 对象:IBinder 接口的通用实现

  • Binder 服务:Binder 对象的实现;例如,位置服务和传感器服务

  • Binder 客户端:使用 Binder 服务的对象

Binder框架包括客户机-服务器通信模型。为了使用IPC,应用程序在代理对象中调用IPC方法。代理对象透明地将调用参数打包并将事务发送到Binder服务器,Binder服务器实现为一个字符驱动程序(/dev/Binder)。服务器持有一个线程池,用于处理传入请求并将消息传递给目标对象。从客户端应用程序的角度来看,所有这些都像是一个常规的方法调用,但繁重的工作都是由Binder框架完成的。

Binder 概述

Binder概述图片来源:
Android Binder by Thorsten Schreiber(https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2011/10/main.pdf

允许其他应用程序绑定的服务称为绑定服务,这些服务必须向客户机提供IBinder接口。开发人员使用Android接口描述符语言(AIDL)为远程服务编写接口。

Servicemanager是一个系统守护进程,它管理系统服务的注册和查找。它维护所有注册服务的名称/Binder对列表。使用addService添加服务,使用android.os.ServiceManager中的静态方法getService通过名称获取服务:

Java 示例:

Kotlin 示例:

你可以通过service list命令查询系统服务的列表。

Intent 消息传递是建立在 Binder 之上的异步通信框架。此框架允许点对点和发布-订阅消息传递。Intent是一个消息传递对象,可以用来请求另一个应用组件的动作。Intent 以几种方式促进组件间的通信,下面是三个基本用例:

  • 开启活动
    活动表示应用程序中的一个界面。你可以通过将 intent 传递给 startActivity 来启动活动的一个新的实例。Intent 描述了活动并携带必要的数据。

  • 开启服务
    服务是在后台执行操作的组件,没有用户界面。在 Android 5.0(API 等级21)及更高版本中,你可以使用 JobScheduler 启动服务。

  • 传递广播
    广播是任何应用程序都能接收到的信息。系统为系统事件传递广播,包括系统引导和充电初始化。你可以通过传递intentsendBroadcastsendOrderedBroadcast,来将一个广播传递给其他应用程序。

Intent有两种类型。显式intent通过将要启动的组件命名(完全限定类名)。例如:

Java示例:

Kotlin示例:

隐式intent被发送给操作系统,以对一组给定的数据(下面示例中的OWASP网站的URL)执行一个给定的操作,由系统决定哪个应用程序或类将执行相应的服务。例如:

Java示例:

Kotlin示例:

Intent过滤器是Android Manifest文件中的一个表达式,它指定组件想要接收的intent类型。例如,通过为活动声明一个intent过滤器,你就可以让其他的应用以某种intent直接启动你的活动。同样地,如果没有为活动声明任何intent过滤器,活动只能被一个显式intent启动。

Android使用intent向应用程序广播消息(如来电或短信)、重要的电源信息(如电池电量不足)和网络变化(如连接中断)。额外的数据可以添加到intent通过putExtra/getExtras)。

下面是操作系统发送的intent的一个简短列表。所有常量都在Intent类中定义,整个列表在官方的Android文档中:

  • ACTION_CAMERA_BUTTON
  • ACTION_MEDIA_EJECT
  • ACTION_NEW_OUTGOING_CALL
  • ACTION_TIMEZONE_CHANGED

为了提高安全性和私密性,本地广播管理器用于在应用程序中发送和接收intent,而不能将intent发送到操作系统的其他部分。这对于确保敏感和私有数据不会离开应用程序的边界(例如地理位置数据)非常有用。

广播接收器

广播接收器是允许应用程序接收来自其他应用程序和系统本身的通知的组件。有了它们,应用程序可以对事件(内部的、由其他应用程序发起的或者由操作系统发起的)作出反应,它们通常用于更新用户界面、启动服务、更新内容和创建用户通知。

有两种方法可以让系统知道一个广播接收器。一种方法是在Android Manifest文件中声明它,清单中应该指定广播接收器和intent过滤器之间的关联,以指示接收器要监听的动作。

一个广播接收器与intent过滤器在清单中的声明例子:

请注意,在本例中,广播接收器不包括android:exported属性。因为至少定义了一个过滤器,所以默认值将被设置为“true”。如果没有任何过滤器,它将被设置为“false”。

另一种方法是在代码中动态创建接收器,接收器可以使用方法Context.registerReceiver注册。

一个动态注册广播接收器的例子:

Java示例:

Kotlin 例子:

需要注意的是,当一个intent被发出时,系统会自动启动一个带有相关已注册接收器的应用程序。

根据广播概述(https://developer.android.com/guide/components/broadcasts),如果不是专门针对应用程序,广播会被认为是“隐式的”。在接收到一个的隐式广播后,Android 会列出所有在其过滤器中注册了特定动作的应用程序。如果有多个应用程序注册了相同的操作,Android会提示用户从可用的应用程序列表中进行选择。

广播接收器的一个有趣功能是可以对它们进行优先级排序;通过这种方式,intent将根据优先级被传递给所有授权的接收者。优先级可以通过清单中的android:priority属性来分配,也可以通过IntentFilter.setPriority方法编程分配。但是请注意,具有相同优先级的接收者将以任意顺序运行(https://developer.android.com/guide/components/broadcasts.html#sending-broadcasts)。

如果你的应用不应该跨应用程序发送广播,可以使用本地广播管理器(LocalBroadcastManager)。它们可以用来确保只接收来自内部应用程序的intent,而来自其他应用程序的intent将被丢弃。这对于提高应用程序的安全性和效率非常有用,因为不涉及进程间通信。但是请注意 LocalBroadcastManager 类已不推荐使用(https://developer.android.com/reference/androidx/localbroadcastmanager/content/LocalBroadcastManager.html),谷歌建议使用LiveDatahttps://developer.android.com/reference/androidx/lifecycle/LiveData.html)等替代方法。

有关广播接收器的更多安全注意事项,请参见安全注意事项和最佳做法(https://developer.android.com/guide/components/broadcasts.html#security-and-best-practices)。

隐式广播接收器限制

根据后台优化(https://developer.android.com/topic/performance/background-optimization),针对 Android 7.0(API 等级24)或更高的应用不再接收CONNECTIVITY_ACTION广播,除非使用Context.registerReceiver()注册广播接收器。系统也不再发送ACTION_NEW_PICTUREACTION_NEW_VIDEO广播。

根据后台执行限制(https://developer.android.com/about/versions/oreo/background.html#broadcasts),针对Android 8.0(API 等级26)及以上的应用,在隐式广播例外情况中列出的除外(https://developer.android.com/guide/components/broadcast-exceptions),不能在其清单中注册隐式广播接收器。在运行时通过调用Context.registerReceiver创建广播接收器不受此限制的影响。

根据系统广播发生的更改,从Android 9(API 等级28)开始,NETWORK_STATE_CHANGED_ACTION广播不再接收用户的位置信息或个人身份信息。

内容提供者

Android 使用 SQLite 来持续化存储数据:与 Linux 一样,数据存储在文件中。SQLite 是一种轻量级、高效的开源关系数据存储技术,不需要太多的处理能力,这使得它非常适合手机使用。SQLite 有包含特定类(Cursor, ContentValues, SQLiteOpenHelper, ContentProvider, ContentResolver 等等)的完整可用 API 。SQLite 不作为单独的进程运行的,它是应用程序的一部分。默认情况下,属于给定应用程序的数据库只能由该应用程序访问。但是,内容提供者提供了一种很棒的来抽象数据源机制(包括数据库和平面文件),它们还提供了一种标准而有效的机制来在应用(包括本地应用)之间共享数据。要让其他应用程序访问内容提供者,需要在共享它的应用程序的清单文件中显式地声明内容提供者。只要没有声明内容提供者,它们就不会被导出,只能由创建它们的应用程序调用。

内容提供者通过 URI 寻址方案实现的:它们都使用了 content:// 模型。无论源的类型是什么(SQLite数据库、平面文件等),寻址方案都是相同的,因此对源进行了抽象,并为开发人员提供了唯一的方案。内容提供者提供了所有常规的数据库操作:创建、读取、更新、删除。这意味着任何在清单文件中拥有适当权限的应用程序都可以操作其他应用程序的数据。

服务

服务是 Android OS 的组件(基于 Service 类),它们在后台执行任务(数据处理、启动 intent 和通知等),而不提供用户界面。服务是用来长期运行进程的,它们的系统优先级低于活动应用程序,而高于不活动应用程序。因此,当系统需要资源时,它们不太可能被杀死,并且可以将它们配置为在有足够的可用资源时自动重启,这使得服务非常适合运行后台任务。请注意,服务和活动一样,都是在主应用程序线程中执行的。服务不会创建自己的线程,也不会在单独的进程中运行,除非另行指定。

权限

由于 Android 应用程序是安装在沙箱中,最初无法访问用户信息和系统组件(比如摄像头和麦克风),Android 为应用程序可以请求的特定任务提供了一套预定义的权限。例如,如果你想让你的应用程序使用手机的摄像头,你必须请求 android.permission.CAMERA 权限。在Android 6.0(API 等级23)之前,应用程序请求的所有权限都在安装时授予。从 API 等级23开始,用户只能在应用程序执行期间同意一些权限请求。

保护等级

Android 的权限是根据其提供的保护级别进行排序的,分为四种不同的类别:

  • Normal:较低的保护级别。 它使应用程序可以访问隔离的应用程序级功能,而对其他应用程序、用户或系统的风险最小。它是在应用安装过程中授予的,并且是默认的保护级别。
    例如:android.permission.INTERNET

  • Dangerous:此权限允许应用程序执行可能影响用户隐私或用户设备正常运行的操作。安装期间可能不会授予此级别的权限,用户必须决定应用程序是否应该有这个权限。
    例如:android.permission.RECORD_AUDIO

  • Signature:只有在请求应用程序与声明该权限的应用程序使用相同的证书签名时,才会授予此权限。如果签名匹配,则自动授予权限。
    例如:android.permission.ACCESS_MOCK_LOCATION

  • SystemOrSignature:此权限仅授予系统映像中嵌入的应用程序或使用与声明该权限的应用相同的证书进行签名的应用程序。
    例如:android.permission.ACCESS_DOWNLOAD_MANAGER

请求权限

应用程序可以通过在其清单中包含

<uses-permission /> 标签来请求正常、危险和签名保护级别的权限。下面的例子是一个请求读取短信权限的 AndroidManifest.xml 示例:

声明权限

应用程序可以向系统上安装的其他应用程序公开功能和内容。为了限制对自己组件的访问,可以使用任何 Android 的预定义权限(https://developer.android.com/reference/android/Manifest.permission.html)或定义自己的权限,使用元素<permission>声明一个新的权限。下面的例子展示了一个应用程序的权限声明:

上面的代码定义了一个新的权限com.permissions.sample.ACCESS_USER_INFO,其保护级别为Signature。任何受此权限保护的组件只能由相同开发者证书签名的应用程序访问。

Android组件的强制权限

Android 组件可以使用权限机制来保护它们的接口。可以通过在AndroidManifest.xml中的相应组件标签中添加属性android:permission来在活动、服务和广播接收器使用强制权限。

内容提供者有一些不同,它们支持一组单独的权限,使用内容 URI 来读取,写入和访问内容提供者。

  • android:writePermissionandroid:readPermission:开发人员可以设置单独的读取或写入权限。
  • android:permission:控制对内容提供者读写的一般权限。
  • android:grantUriPermissions:如果可以使用内容URI 访问内容提供者,则为“ true”(访问暂时绕过其他权限的限制),否则为“ false”。

签名和发布流程

一旦应用程序成功开发,下一步就是发布分享给其他人。然而,应用程序不能简单地添加到商店并共享,它们必须首先签名,该加密签名作为应用程序开发者放置的可验证标记。它识别应用程序的作者,并确保应用程序自最初发布以来没有被修改过。

签名过程

在开发过程中,应用程序使用自动生成的证书进行签名,此证书本质上是不安全的并且仅用于调试。大多数商店不接受这种证书进行发布,因此,必须创建具有更安全的特性的证书。当一个应用程序安装在 Android 设备上时,包管理器会确保它是用包含在相应 APK 中的证书进行的签名。如果证书的公钥与用于设备上任何其他 APK 签名的密钥相匹配,那么新的 APK 可能与已存在的 APK 共享一个 UID。这促进了来自单个供应商的应用程序之间的交互,或者,指定的安全权限 对 Signature 保护级别来说是可能的,这将限制对使用相同密钥签名的应用程序的访问。

APK 签名方案

Android 支持三种应用签名方案。从 Android 9(API level 28)开始,APK 可以通过 APK 签名方案 v3 、APK 签名方案 v2 或 JAR 签名(v1 方案)进行验证。对于 Android 7.0 (API 等级24)及以上版本,可以通过 APK 签名方案 v2 或 JAR 签名(v1方案)对 APK 进行验证。为了向后兼容,APK 可以使用多种签名方案进行签名,以使应用程序在较新的和较旧的 SDK 版本上都运行。旧的平台会忽略 v2 签名,只验证 v1 签名(https://source.android.com/security/apksigning/)。

JAR 签名(v1 方案)

应用程序签名的原始版本将签名的 APK 实现为标准的签名 JAR,它必须包含 META-INF/MANIFEST.MF 中的所有条目,所有文件都必须使用一个公共证书签名,这个方案不保护 APK 的某些部分,比如 ZIP 元数据。该方案的缺点是 APK 验证者在应用签名之前需要对不可信的数据结构进行处理,并且验证者会丢弃数据结构不包含的数据。此外,APK 验证者必须解压所有压缩文件,这需要大量的时间和内存。

APK 签名方案(v2方案)

在 APK 签名方案中,对完整的 APK 进行哈希和签名,然后创建一个 APK 签名分块并插入到 APK 中。在验证期间,v2 方案检查整个 APK 文件的签名。这种形式的 APK 验证速度更快,并且提供了更全面的防篡改保护。你可以在下面看到 v2 方案的 APK 签名验证过程(https://source.android.com/security/apksigning/v2#verification)。

apk 签名验证过程 v2 方案

APK 签名方案(v3 方案)

v3 APK 签名块格式与 v2相 同。v3 将有关受支持的 SDK 版本和 proof-of-rotation 结构的信息添加到 APK 签名分块中。在 Android 9(API 等级28)及以上版本中,可以根据APK签名方案 v3、v2 或 v1 方案对 APK 进行验证。旧的平台会忽略 v3 签名尝试验证 v2 签名,然后验证v1签名。

签名数据部分中的 proof-of-rotation 属性包含一个单链表,其中每个节点都包含用于为之前版本的应用签名的签名证书。为了实现向后兼容性,
系统会让每个节点中的证书为列表中的下一个证书签名,从而为每个新密钥提供证据来证明它应该像旧密钥一样可信。单独对 APK 签名已经不可能了,因为 proof-of-rotation 结构必须使用旧的签名证书来对新的证书集签名,而不是一个一个地对它们签名。你可以在下面看到APK签名 v3 方案验证过程。(https://source.android.com/security/apksigning/v3

apk 签名验证过程 v3 方案

创建证书

Android 使用公共/私有证书来签名 Android 应用程序(.apk文件)。证书包含了大量的信息,就安全性而言,密钥是是最重要的信息类型。公共证书包含用户的公钥,私有证书包含用户的私钥。公共证书和私有证书是链接的。证书是唯一的不能重新生成,请注意,如果一个证书丢失了它就无法恢复,因此更新任何使用该证书签名的应用程序将变得不可能。应用程序创建者可以重用可用密钥存储库中现有的私有/公共密钥对,也可以生成新的密钥对。在 Android SDK 中,使用 keytool 命令生成一个新的密钥对。下面的命令创建一个密钥长度为2048位、过期时间为7300天即20年的 RSA 密钥对。生成的密钥对存储在当前目录中的文件 'myKeyStore.jks' 中):

安全地存储你的密钥并确保它在整个生命周期中保持机密性是极其重要的。任何获得密钥访问权限的人都可以将你无法控制的内容(从而添加不安全的特性或使用基于签名的权限访问共享内容)更新到你的应用程序中。用户对应用程序及其开发者的信任完全基于这些证书,因此,证书保护和安全管理对于声誉和客户去留至关重要,密钥永远不能与其他个人共享。密钥存储在可以用密码保护的二进制文件中,这样的文件被称为密钥存储库。密钥存储库的密码应该是强壮的,并且只有密钥创建者知道。因此,密钥通常存储在开发人员对其访问受限的专用构建机器上。Android 证书的有效期必须超过相关应用程序(包括更新版本的应用程序),例如,Google Play 要求证书至少在2033年10月22日之前保持有效。

签名一个应用程序

签名过程的目标是将应用程序文件(.apk)与开发人员的公钥关联起来。为了实现这一点,开发人员计算 APK 文件的哈希,并用他们自己的私钥加密它。第三方可以使用作者的公钥解密加密的哈希来验证应用程序的真实性(例如,应用程序确实来自声称是发起者的用户),验证它与 APK 文件的真实哈希匹配。

许多集成开发环境(IDE)集成了应用程序签名过程让用户更容易使用。请注意,有些 IDE 在配置文件中以明文存储私钥,请仔细检查,以防其他人能够访问这些文件并在必要时删除这些信息。通过 Android SDK(API 等级24及以上)提供的“apksigner”工具,可以从命令行中对应用程序进行签名。它位于 [SDK-Path]/build-tools/[version]。对于 API 24.0.2 及以下版本,可以使用”jarsigner“,它是 Java JDK 的一部分。整个过程的细节可以在 Android 官方文档中找到,下面给出了一个例子来说明这一点。

在这个示例中,一个未签名的应用程序(myUnsignedApp.apk)将使用来自开发人员密钥存储库myKeyStore.jks(位于当前目录中)的私钥进行签名。该应用程序将成为名为mySignedApp.apk的已签名应用程序,并准备发布到商店。

Zipalign

在发布之前,应该始终使用zipalign工具来对齐 APK 文件。这个工具在 APK 中对齐所有未压缩的数据(如图像、原始文件和4字节边界),这有助于在应用程序运行时改进内存管理。

在使用 apksigner 签名 APK 文件之前,必须使用 Zipalign。

发布过程

因为 Android 生态系统是开放的,所以可以在任何地方(你自己的网站,任何商店等等)发布应用程序。然而,Google Play 是最知名、最受信任和最受欢迎的商店,Google 本身就提供这种服务。亚马逊应用商店是 Kindle 设备的默认可信的商店。如果用户想要从不受信任的来源安装第三方应用程序,他们必须在设备安全设置中明确允许这样做。

安卓设备上可以通过多种渠道安装应用:本地USB、Goolgle 官方应用商店(Google Play Store)或其他商店。

鉴于其他供应商可能会在应用程序真正发布前对其进行审查和批准,谷歌只会扫描已知的恶意软件签名,这将缩短发布过程的开始到公开应用程序可用性之间的时间。

发布一个应用程序非常简单,主要操作是使签名 APK 文件可下载。在 Google Play 上,发布从创建账号开始,然后通过专用界面发布应用程序。详细信息可以在 Android 官方文档(https://developer.android.com/distribute/best-practices/launch)中找到。

安卓应用攻击层面

Android 应用程序攻击面由应用程序的所有组件组成,包括发布应用程序和支持其功能所需的支持材料。Android应用程序可能容易受到攻击如果不这样做:

  • 通过 IPC 通信或 URL 方案验证所有输入,见:
    通过 IPC 测试敏感功能暴露
    测试自定义URL方案

  • 验证用户在输入字段中的所有输入。

  • 验证在 WebView 中加载的内容,见:
    在 Webview 中测试 JavaScript 执行
    测试 WebView 协议处理程序
    确定是否通过 Webview 公开 Java 对象

  • 安全地与后端服务器通信,否则易在服务器和移动应用程序之间受到中间人攻击
    测试网络通信
    安卓网络 API

  • 安全存储所有本地数据,不从存储中加载不可信的数据,参见:
    Android 上的数据存储

  • 保护自己免受环境破坏,如重新打包或其他本地攻击,见:
    Android反逆向防御

Android 基础安全测试

基础的安卓测试步骤

到目前为止,你应该对 Android 应用程序的结构和部署方式有了基本的了解。在本章中,我们将讨论如何建立一个安全测试环境,并描述您将使用的基本测试流程。本章是后面章节中讨论的更详细的测试方法的基础。
您可以在几乎所有运行 Windows、Linux 或 Mac OS 的机器上设置一个功能完整的测试环境。

主机设备

至少,你需要 Android Studio(附带 Android SDK)平台工具、一个模拟器和一个应用程序来管理各种 SDK 版本和框架组件。Android Studio 还附带了一个用于创建模拟器映像的 Android 虚拟设备(AVD)管理器应用程序。确保你的系统上安装了最新的 SDK 工具平台工具包

此外,如果你打算使用包含原生库的应用程序,你可能想要通过安装 Android NDK来完成主机设置(这在“Android 上的篡改和逆向工程”一章中也会提到)。

设置 Android SDK

通过 Android Studio 来管理本地的 Android SDK 安装。在 Android Studio 中创建一个空项目并选择 Tools -> SDK Manager 来打开 SDK Manager GUI。SDK Platform 选项卡上可以安装多个 API 等级的 SDK。最近的 API 等级为:

  • Android 10.0(API 等级29)
  • Android 9.0(API 等级28)
  • Android 8.1(API 等级27)
  • Android 8.0(API 等级26)

所有 Android 代号、版本号和 API 等级的概述可以在 Android 开发者文档中找到。

已安装的 SDK 位于以下路径:

Windows:

MacOS:

注意:在Linux上,你需要选择一个SDK目录,/opt/srv 和 /usr/local是常见的选择。

设置 Android NDK

Android NDK 包含原生编译器和工具链的预编译版本。GCC 和 Clang 编译器在传统上都得到了支持,但是对 GCC 的积极支持在 NDK第14版结束了。设备体系结构和主机操作系统决定适当的版本,预编译的工具链在 NDK 的toolchains目录中,每种架构包含一个相应的子目录。

除了选择正确的体系结构之外,你还需要为想要目标 Native API 等级指定正确的 sysroot。sysroot 是一个包含目标系统头文件和库的目录。Native API 因 Android API 等级的不同而不同。每个Android API 等级的 sysroot 目录都可以在 $NDK/platforms/ 中找到。每个API级别目录都包含各种 CPU 和体系结构的子目录。

设置编译系统的一种可能是将编译器路径和必要的标记导出为环境变量。不过,为了让事情变得更简单,NDK 允许你创建一个所谓的独立工具链,这是一个临时的工具链,包含了所需的设置。

要建立一个独立的工具链,需要下载 NDK 最新的稳定版本。解压缩 ZIP 文件,切换到 NDK 根目录,运行以下命令:

这将在/tmp/android-7-toolchain目录中为Android 7.0(API 等级24)创建一个独立的工具链。为了方便,可以导出一个指向工具链目录的环境变量(我们将在示例中使用它)。运行以下命令或将它添加到 .bash_profile 中或其他启动脚本:

测试设备

为了进行动态分析,你需要一个 Android 设备来运行目标应用程序。原则上,你可以在没有真正的 Android 设备的情况下进行测试,并且只使用模拟器。然而,应用程序在模拟器上执行得相当慢,模拟器可能不会给出真实的结果。在真实的设备上进行测试可以使过程更加顺畅,环境也更加真实。另一方面,模拟器允许你轻松地更改 SDK 版本或创建多个设备。下表列出了每种方法的优缺点。

Property Physical Emulator/Simulator
恢复能力 变成软砖总是可能的,但通常仍可以刷机,变成黑砖很少见。 模拟器可能会崩溃或损坏,但可以创建一个新的模拟器或恢复一个快照。
重置 可以恢复到工厂设置或刷机。 可以删除和重新创建模拟器。
快照 不可能。 更好地支持恶意软件分析。
速度 比模拟器快得多。 通常比较慢,但正在进行改进。
成本 可用的设备一般起价200美元,你可能需要不同的设备,例如带有或不带有生物识别传感器的设备。 免费的和商业的解决方案都存在。
root 难度 高度依赖于设备。 通常默认 root
模拟器检测难度 不是一个模拟器,模拟器检测不使用 将存在许多伪像,从而可以轻松检测到该应用程序正在模拟器中运行。
root 检测难度 隐藏 root 更容易,因为许多 root 检测算法都会检查模拟器属性, 借助Magisk Systemless,root 几乎无法检测到。 模拟器几乎总是会触发 root 检测算法,这是因为模拟器是为测试许多可发现的伪像而编译的。
硬件交互 通过蓝牙,NFC,4G,Wi-Fi,生物识别,相机,GPS,陀螺仪...轻松交互 通常相当有限,模拟硬件输入(例如,随机GPS坐标)
API 等级支持 取决于设备和社区。活跃的社区会不断发布更新的版本(比如 LineageOS),而不太流行的设备可能只会收到一些更新。在版本之间切换需要刷机,这是一个乏味的过程。 始终支持最新版本,包括 beta 版本。可以轻松下载和启动包含特定 API 等级的模拟器。
原生库支持 原生库通常是为 ARM 设备编译的,所以它们会在一个物理设备上工作 一些模拟器运行在 x86 CPU 上,因此它们可能不能运行打包的原生库。
恶意软件危险 恶意软件样本可以感染设备,但如果你可以清除设备存储并刷机,从而将其恢复到出厂设置,这应该不是问题。请注意,有些恶意软件样本会试图利用 USB 网桥。 恶意软件样本可以感染模拟器,但模拟器可以简单地删除和重新创建。也可以创建快照并比较不同的快照来帮助分析恶意软件。请注意,有证据存在试图攻击 hypervisor 的恶意软件。

在一个真实设备上测试

几乎任何物理设备都可以用于测试,但是有一些需要考虑的事项。首先,设备需要 root,通常通过漏洞利用或未锁定的引导加载程序来完成此操作。 漏洞利用并不总是可用的,并且引导加载程序可能被永久锁定,或者只有在运营商合同终止后才能被解锁。

最佳候选设备是为开发者打造的旗舰 Google pixel 设备。这些设备通常都带有未锁定的引导加载程序、开源的固件、内核、在线电台和官方操作系统源代码。开发者社区更喜欢 Google 设备,因为该操作系统最接近 android 开源项目。 这些设备通常具有最长的支持窗口,具有2年的 OS 更新和1年的安全更新。

另外,Google 的 Android One 项目包含的设备将会得到同样的支持窗口(2年的操作系统更新,1年的安全更新),并且有接近原生的经验。虽然它最初是一个针对低端设备的项目,但该项目已经发展到包括中端和高端智能手机,其中许多都得到了 modding 社区的积极支持。

LineageOS项目支持的设备也是测试设备的很好的候选设备。他们有一个活跃的社区,易于遵循的刷机和 root 说明,并且通常可以通过 Lineage 安装快速获得最新的 Android 版本。在 OEM 停止发布更新后很长一段时间里,LineageOS 还继续支持新的 Android 版本。

当使用 Android 物理设备时,你需要启用开发者模式和设备上的 USB 调试,以便使用 ADB 调试接口。自 Android 4.2(API 等级16)以来,手机设置菜单中开发者选项默认是隐藏的,要激活它,点击关于手机中的版本号部分七次。请注意,版本号字段的位置因设备略有不同。例如,在LG手机上,它是 About phone -> Software information。一旦你这样做了,开发者选项将显示在设置菜单的底部。一旦开发者选项被激活,你可以使用 USB 调试开关启用调试。

在模拟器上调试

存在多种模拟器,它们各有优缺点:

免费模拟器:

商业模拟器:

  • Genymotion - 具有许多特性的成熟仿模拟器,可以作为本地和基于云的解决方案,提供非商业使用的免费版本。

  • Corellium - 通过基于云或本地的解决方案提供自定义设备虚拟化。

虽然有其他一些免费的 Android 模拟器,但我们推荐使用 AVD,因为它提供了与其他模拟器相比更适合测试应用程序的增强功能。在本指南的其余部分中,我们将使用官方的 AVD 来执行测试。

AVD 支持一些硬件仿真,如 GPSSMS 和运动传感器

你可以使用 Android Studio 中的 AVD 管理器启动 Android 虚拟设备(AVD),也可以在命令行中使用android命令启动 AVD 管理器,在 Android SDK 的 tools 目录中可以找到:

可以使用一些工具和 VM 在模拟器环境中测试应用程序:

获取特权访问

Rooting(例如,修改 OS 以便你可以作为 root 用户运行命令)建议在真实设备上进行测试。这使你能够完全控制操作系统,并允许您绕过应用程序沙盒之类的限制。这些特权又允许你更容易地使用代码注入和函数 hook 等技术。

注意,root 是有风险的,在你继续之前,需要弄清三个主要后果。Root 可产生以下负面影响:

  • 取消设备保修(在采取任何行动之前,一定要检查制造商的政策)
  • 设备变砖,即设备无法操作和使用
  • 创建额外的安全风险(因为通常会删除内置的漏洞缓解措施)

你不应该 root 一个存储着私人信息的个人设备,我们建议购买一个便宜的专用测试设备。许多老的设备,比如谷歌的 Nexus 系列,都可以运行最新的 Android 版本,用来测试非常好。

你需要明白,root 你的设备最终是你自己的决定,OWASP 不会对任何损坏负责。如果你不确定,在开始 root 过程之前应该寻求专家的建议。

哪些手机可以 root

实际上,任何 Android 手机都可以 root。Android OS(在内核级是 Linux OS 的演化)的商业版本针对移动世界进行了优化。这些版本中,一些特性被删除或禁用了,例如,非特权用户可以成为 root 用户(拥有提升的特权)。Root 手机意味着允许用户成为 root 用户,例如,添加一个名为 su 的标准 Linux 可执行文件,可用于更改为另一个用户帐户。

要 root 一个移动设备,首先解锁它的启动引导程序,解锁的过程取决于设备制造商。然而,出于实际的原因,root 某些移动设备要比其他设备更受欢迎,尤其是在安全测试方面:Google 开发的,Samsung、LG 和 Motorola 等公司制造的设备最受欢迎,尤其是因为它们被许多开发人员使用。当引导加载程序被解锁时,设备保证不会失效,谷歌提供了许多工具来支持 root。XDA 论坛上发布了精选的所有主流品牌设备的 root 指南。

使用 Magisk 获得 root 权限

Magisk(“Magic Mask”)是一种 root 你的 Android 设备的方法,它的特殊性在于对系统进行修改的方式。当其他的 root 工具改变系统分区上的实际数据时,Magisk 不会(它被称为“systemless“)。这样就可以隐藏对 root 敏感的应用程序(例如银行或游戏)的修改,并且在 root 的情况下允许使用官方的 Android OTA 升级

你可以通过阅读 GitHub 上的官方文档来熟悉 Magisk。如果您没有安装 Magisk,可以在文档中找到安装说明。如果你使用正式的 Android 版本并计划升级,Magisk 在 GitHub 上提供了一个教程

此外,开发人员可以使用 Magisk 的强大功能创建自定义模块,并将它们提交到官方的 Magisk 模块库,提交的模块可以安装在 Magisk 管理器应用程序中。著名的 Xposed 框架的 systemless 版本(适用于高达27的 SDK 版本)是这些可安装模块之一。

Root 检测

Root 检测方法的一个详细列表在“在 Android 上测试反逆向防御”一章中被提出。

对于典型的移动应用程序安全性编译版本,你通常希望测试一个禁用 root 检测的调试版本编译。如果这样的编译无法用于测试,你可以使用各种方法禁用 root 检测,这将在本书后面介绍。

Android 设备上推荐的工具

本指南中使用了许多工具和框架来评估 Android 应用程序的安全性。在下一节中,你将了解更多一些命令和有趣的使用案例,请参阅官方文档了解有关下列工具的安装说明:

  • APK Extractor:不需要 root 即可提取 APK 的应用程序。

  • Frida server:Frida 的服务器,适用于开发人员、逆向工程人员和安全研究人员的动态工具套件。有关更多信息见下面的 Frida 部分。

  • Drozer代理:drozer 的代理,该框架使您可以搜索应用程序和设备中的安全漏洞。有关更多信息见下面的 Drozer 部分。

  • Busybox:Busybox 将多个常见的 Unix 实用程序组合成一个小的可执行文件。通常,所包含的实用程序比其功能齐全的同类程序 GNU 具有更少的选择,但足以在小型或嵌入式系统上提供完整的环境。Busybox 可以安装在 root 设备上,通过从 Google Play 商店可以下载 Busybox 应用程序。你也可以直接从 Busybox 网站下载二进制文件。下载后,运行adb push busybox /data/local/tmp传输文件到手机上。在 Busybox FAQ 中可以找到有关如何安装和使用 Busybox 的快速概述。

Xposed

Xposed 是一个“无需更改 APK 即可更改系统和应用程序行为的模块框架。”从技术上讲,它是Zygote的扩展版本,在启动新进程时会导出用于运行 Java 代码的 API。在新实例化的应用程序上下文中运行 Java 代码可以解析、hook 和重写属于该应用程序的 Java 方法。Xposed 使用反射来检测和修改正在运行的应用程序,修改会应用到内存中,并且仅在进程的运行时持久保存,并未修改应用程序二进制文件。

要使用 Xposed,首先需要像 XDA-Developers Xposed framework hub 上解释的那样,在一个 root 设备上安装 Xposed 框架。模块可以通过 Xposed 安装程序安装,通过 GUI 打开和关闭它们。

注意:考虑到 SafetyNet 很容易检测到 Xposed 框架的简单安装,我们建议使用 Magisk 来安装 Xposed。这样,带有 SafetyNet 认证的应用程序会具有更高的使用 Xposed 模块进行测试的机会。

Xposed 已与 Frida 进行了比较。 在 root 设备上运行 Frida server 时,最终你将得到一个同样有效的设置。当你要进行动态检测时,这两个框架都十分有用。当 Frida 使应用程序崩溃时,你可以尝试在 Xposed 上使用类似的方法。接下来,类似于大量的 Frida 脚本,你可以轻松地使用 Xposed 的众多的模块,例如前面讨论的绕过 SSL pinning 模块(JustTrustMe 和 SSLUnpinning)。Xposed 还包括其他模块,例如 Inspeckage,它使你能够进行应用程序更多的深度测试。最重要的是,你还可以创建自己的模块,以修改 Android 应用程序的常用安全机制。

Xposed 也可以通过下面的脚本安装在模拟器上:

请注意,在撰写本文时,Xposed 无法在 Android 9(API 等级28)上运行。但是在2019年它以 Edxposed 的名字被非正式地移植,支持Android 8-10(API 等级26至29),你可以在 EdXposed 的 Github 仓库中找到代码和用法示例。

主机上推荐的工具

为了分析Android应用程序,你应该在你的主机上安装以下工具。请在官方文档中查看以下工具或框架的安装说明,我们将在指南中提到它们。

Adb

adb(Android Debug Bridge),随 Android SDK 一起提供,连接本地开发环境和已连接的 Android 设备。你通常会使用它在模拟器或通过 USB 或 Wi-Fi 连接的设备上测试应用程序。使用adb devices命令可以列出连接的设备,执行时并加上-l参数可以检索有关这些设备的更多细节。

adb 提供了其他有用的命令,比如adb shell在目标设备上启动交互式的shell,以及adb forward将特定主机端口上的流量转发到连接设备上的不同端口。

请注意,如果连接了多个设备,则必须使用-s参数定义目标设备的序列号(如上一代码片段所示)。

Angr

Angr 是一个用于分析二进制文件的 Python 框架。它对于静态和动态符号分析都是有用的。换句话说:给定一个二进制和一个请求状态,Angr将尝试到达那个状态,使用形式化的方法(一种用于静态代码分析的技术)来找到一条路径,以及强制执行。使用 angr 来获得请求的状态通常比手动调试和搜索通往所需状态的路径要快得多。Angr 使用VEX 中间语言进行操作,并带有 ELF/ARM 二进制文件加载程序,因此非常适合处理原生代码,例如原生 Android 二进制文件。

Angr 允许使用大量插件来进行反汇编、程序检测、符号执行、控制流分析、数据依赖分析、反编译等等。

自从版本8,Angr 是基于 Python3 并可以使用 pip 安装在*nix操作系统、macOS 和 Windows 上:

angr 的一些依赖项包含 Python 模块 Z3 和 PyVEX 的派生版本,这将覆盖原始版本。如果你将这些模块用于其他用途,则应该使用 Virtualenv 创建一个专用的虚拟环境。另外,你也可以使用提供的 docker 容器。有关更多细节,请参阅安装指南

在 Angr 的 Gitbooks 页面上有全面的文档,包括安装指南、教程和使用示例,还提供了完整的 API 参考

你可以通过 Python REPL(如 iPython)使用angr,也可以编写方法脚本。尽管 angr 的学习曲线有些陡峭,但是尽管 angr 的学习曲线有些陡峭,但是当你想要通过强制的方式得到一个可执行文件的给定状态,我们还是建议你使用它。请见“逆向工程和篡改”一章的“符号执行”部分,可以作为一个很好的例子来说明它是如何工作的。

Apktool

Apktool 用于解压 Android 应用程序包(APK)。简单地用标准 unzip工具解压 APK 会留下一些不可读的文件。AndroidManifest.xml 被加密成二进制 XML 格式,文本编辑器无法读取。此外,应用程序资源仍然被打包到一个归档文件中。

当使用默认命令行参数运行时,apktool 会自动将 Android 清单文件解密为为基于文本的 XML 格式并提取文件资源(它还会将 .DEX 文件反汇编为 smali 代码——这个特性我们将在本书后面介绍)。

解压缩的文件是:

  • AndroidManifest.xml:解密的 Android Manifest 文件,可以在文本编辑器中打开和编辑该文件。
  • apktool.yml:包含有关 apktool 输出信息的文件
  • original:包含 MANIFEST.MF 文件的文件夹,MANIFEST.MF 文件包含有关 JAR 文件中存在的文件的信息
  • res:包含应用程序资源的目录
  • smali:包含反汇编的 Dalvik 字节码的目录

还可以使用 apktool 将已解密的资源重新打包回二进制 APK/JAR。见本章后面的“探索应用程序包”一节和”Android 上的篡改和逆向工程“一章中“重打包”一节,了解更多信息和实例。

Apkx

Apkx是流行的免费 DEX 转换器和 Java 反编译器的 Python 包装器。它自动提取、转换和反编译 apk,安装方式如下:

这会将apkx复制到/usr/local/bin。请参阅“逆向工程和篡改”一章的“反编译Java代码”一节,了解更多有关用法的信息。

Burp Suite

Burp Suite 是一个用于移动和 web 应用程序安全性测试的集成平台。它的工具可以无缝地协同工作,以支持整个测试过程,从最初的攻击面映射和分析到发现和利用安全漏洞。Burp Proxy 作为 Burp Suite 的 web 代理服务器,它被定位为浏览器和 web 服务器之间的中间人。Burp Suite 允许您拦截、检查和修改传入和传出的原始 HTTP 流量。

设置 Burp 代理您的流量非常简单。我们假设您有一个 android 设备和工作站连接到 Wi-Fi 网络,该网络允许客户端到客户端通信。

PortSwigger 提供了关于如何设置Android 设备来使用 Burp 如何将 Burp 的 CA 证书安装到 Android 设备的教程

Drozer

Drozer 是一个 Android 安全评估框架,如果第三方应用程序与其他应用程序的 IPC 端点和底层操作系统进行了交互,你可以搜索应用程序和设备中的安全漏洞。

使用 drozer 的优势在于它能够自动执行多个任务,并且可以通过模块进行扩展。这些模块非常有帮助并且涵盖了不同的类别,其中包括扫描器类别,该类别使你可以使用简单的命令扫描已知的缺陷,例如模块scanner.provider.injection可以检测系统中安装的所有应用程序的内容提供者中的SQL注入 。 如果不使用drozer,则简单的任务(例如列出应用程序的权限)需要几个步骤,包括反编译 APK 和手动分析结果。

安装 Drozer

你可以参考 drozer GitHub 页面(对于 Linux 和 Windows,macOS 请参考这篇博客文章)和 drozer 的网站了解必备条件和安装说明。

drozer 在 Unix、Linux 和 Windows 上的安装说明在 drozer Github 页面中有解释。对于macOS,这篇博客演示了所有的安装说明。

使用 Drozer

在开始使用 drozer 之前,还需要在 Android设备上运行 drozer 代理。从 GitHub 发布页面下载最新的 drozer 代理,并用 adb install drozer.apk 安装它。

一旦安装完成,你可以通过运行 adb forward tcp:31415 tcp:31415 和 drozer console connect 启动一个会话到模拟器或 USB 连接的设备。这被称为直接模式,你可以在用户指南的“开始一个会话”一节中看到完整的说明。另一种选择是在基础设施模式下运行Drozer,在这种模式下,你运行一个 Drozer 服务器,它可以处理多个控制台和代理,并在它们之间路由会话。你可以在用户指南的“基础设施模式”一节中找到如何在此模式下设置 drozer 的详细信息。

现在您可以开始分析应用程序了。一个好的开端是列举一个应用程序的攻击面,可以通过以下命令很容易地完成:

同样,如果没有 drozer,这将需要几个步骤。app.package.attacksurface模块列出了活动,广播接收者,内容提供者和导出的服务,因此它们是公共的并且可以通过其他应用程序进行访问。 一旦确定了攻击面,就可以通过 drozer 与 IPC 端点进行交互,而无需编写单独的独立应用程序,某些任务会需要它,比如与内容提供者通信。

例如,如果应用程序的导出活动泄漏了敏感信息,我们可以使用 Drozer 模块app.activity.start来调用它:

前面的命令将启动活动,希望泄漏一些敏感信息。Drozer 有针对每一种 IPC 机制的模块,如果你想尝试带有故意易受攻击的应用程序的模块,请下载 InsecureBankv2,该应用程序演示了与 IPC 端点相关的常见问题。请密切注意扫描器类别中的模块,因为它们对于自动检测系统包中的漏洞非常有用,特别是如果你使用的是手机公司提供的 ROM。在过去甚至使用 drozer 识别出过 Google 系统软件包中的 SQL 注入漏洞。

其他的 Drozer 命令

这里有一个非详尽的命令列表,你可以用来开始探索 Android:

其他的 Drozer 资源

你可能会发现其他有用信息的资源有:

Frida

Frida 是一个免费和开源的动态代码工具包,它允许你在本地应用程序中执行 JavaScript 脚本,在通用测试指南的“篡改与逆向工程”一章中已经介绍了。

Frida 通过 Java API 支持与 Android Java 运行时的交互。你能够在hook 和调用进程的 Java 和原生函数,以及它的原生库。你的 JavaScript 脚本对内存有完全的访问权限,例如读取或写入任何结构化数据。

下面是 Frida api 提供的一些任务,在 Android 上是相关的或独家的:

  • 实例化 Java 对象并调用静态和非静态类方法(Java API )。

  • 替换Java方法实现(Java API )。

  • 通过扫描 Java 堆枚举特定类的活动实例(Java API )。

  • 扫描进程内存中出现的字符串(Memory API )。

  • 拦截原生函数调用以在函数入口和出口运行自己的代码(Interceptor API)。

记住,在Android上,你还可以从安装 Frida 时提供的内置工具中获益,包括 Frida CLI(frida)、frida-psfrida-ls-devices 和 frida-trace

Frida 经常被拿来和 Xposed 进行比较,但是这种比较并不公平,因为这两个框架的设计目标是不同的。作为一个应用程序安全测试人员,了解这一点很重要,这样你就可以知道在什么情况下使用哪个框架:

  • Frida 是独立的,你所需要做的就是从目标 Android 设备中的已知位置运行 frida-server 二进制文件(请参阅下面的“安装Frida”)。这意味着,与 Xposed 相比,它没有深入安装在目标 OS 中。

  • 逆向一个应用程序是一个反复的过程。由于上一点的影响,在测试时,你获得了更短的反馈循环,因不需要(软)重启来应用钩子或简单地更新钩子。同时在实现更持久的钩子时,你可能更喜欢使用 Xposed。

  • 你可以在进程运行期间的任何时候动态注入和更新 Frida JavaScript 代码(类似于 iOS 上的 Cycript)。这样,可以通过让 Frida 来 spwan 你的应用程序执行所谓的早期检测,或者你可能更喜欢附加到一个进入特定状态的正在运行的应用程序。

  • Frida 能够处理 Java 以及原生代码(JNI),允许你修改它们。不幸的是,这正是 Xposed 的局限性,缺乏原生代码支持。

值得注意的是,到 2019 年初,Xposed 还不能在 Android 9(API 等级28)上运行。

安装 Frida

要在本地安装 Frida,只需运行:

参考安装页面了解更多细节。

下一步就是在你的 Android 设备上设置 Frida:

  • 如果您的设备没有 root,你也可以使用Frida,请参考“逆向工程和篡改”章节的“非 root 设备的动态分析”。
  • 如果你有一个已 root 的设备,只需遵循官方说明或下面的提示。

除非另有说明,我们假定在这里是 root 设备。从 Frida 发布页下载 frida-serve 二进制文件。确保为你的 Android 设备或模拟器的架构下载了正确的 frida-server 二进制文件:x86,x86_64,arm 或 arm64。确保服务器版本(至少是主版本号)与本地 Frida 安装版本匹配。PyPI通常安装最新版本的 Frida。如果不确定安装的版本,可以使用 Frida 命令行工具检查:

或者你可以运行以下命令来自动检测 Frida 版本并下载正确的 frida-server 二进制文件:

复制 frida-server 到设备上并运行它:

在 Android 上使用 Frida

随着 frida-server 的运行,你现在应该可以使用以下命令获得正在运行的进程的列表(用 -U 选项指示 Frida 使用一个已连接的 USB 设备或模拟器):

或使用 -Uai 参数组合限制列表,以获取连接的 USB 设备(-U)上当前安装的(-i)所有应用程序(-a):

这将显示所有应用程序的名称和标识符,如果目前正在运行,还将显示它们的 PID。在列表中搜索你的应用程序,并注意 PID 或其名称/标识符,从现在起你将使用其中一个来引用你的应用程序。建议使用这些应用的标识符,因为在每次运行应用程序时 PID 都会改变。例如,以 com.android.chrome 为例,你现在可以在所有 Frida 工具上使用此字符串,如在 Frida CLI,frida-trace 或 Python 脚本上。

使用 frida-trace 跟踪原生库

要跟踪特定的(底层)库调用,你可以使用 frida-trace 命令行工具:

这会在 __handlers __ / libc.so/open.js 中生成一些 JavaScript 代码,Frida 将其注入到进程中。该脚本将跟踪对 libc.so 中的 open 函数的所有调用。你可以使用 Frida JavaScript API 根据需要修改生成的脚本。

不幸的是,尚不支持跟踪 Java 类的高级方法(但将来可能会)。

Frida CLI 和 Java API

使用 Frida CLI 工具(Frida)与 Frida 交互工作。它挂接到一个进程,并为你提供 Frida API 的命令行界面。

通过 -l 选项,你可以使用 Frida CLI 加载脚本,例如,加载 myscript.js

Frida 还提供了一个 Java API,这对处理 Android 应用程序特别有帮助,它允许你直接使用 Java 类和对象。下面是一个脚本,用于重写 Activity 类的 onResume 函数:

上面的脚本调用 Java.perform 以确保您的代码在 Java VM 的环境中执行。它通过 Java.use 实例化一个 android.app.Activity 类的包装器,并重写 onResume 函数。新的 onResume 函数实现打印通知到控制台,并在活动每次处于恢复状态时通过调用 this.onResume 来调用原来的 onResume 方法。

Frida 还允许你搜索并使用堆上的实例化对象。以下脚本搜索 android.view.View 对象的实例,并调用其 toString 方法,结果打印到控制台:

输出会像这样:

你还可以使用 Java 的反射功能。要列出 android.view.View 类的公共方法,你可以在 Frida 中为此类创建一个包装器,并从该包装器的 class 属性调用 getMethods

这将在终端打印很长的方法列表:

Frida Binding

为了扩展脚本编写体验,Frida 提供了与Python,C,NodeJS 和 Swift 等编程语言的绑定。

以 Python 为例,首先要注意的是不需要更多的安装步骤。使用 import frida 启动 Python 脚本,就可以开始了。请参阅下面的脚本,该脚本仅运行先前的 JavaScript 代码段:

在这种情况下,运行 Python 脚本(python3 frida_python.py)与上一个示例具有相同的结果:它会打印 android.view.View 类的所有方法到终端。但是,你可能希望使用 Python 中的数据。使用 send 代替 console.log 会将数据以 JSON 格式从 JavaScript 发送到 Python 上。请阅读以下示例中的注释:

这有效地过滤了方法,只打印包含字符串“Text”的方法:

最后,由你决定在什么地方处理数据,有时候,用 JavaScript 来做会比较方便,而在其他情况下,Python 将是最好的选择。当然,你也可以使用 script.post 将消息从 Python 发送到 JavaScript 上。有关发送接收消息的更多信息,请参考 Frida 文档。

House

House 是一个用于 Android 应用运行时的移动应用分析工具包,由 NCC 小组开发和维护,用 Python 编写。

它利用 root 设备上运行的 Frida server 或重打包到 Android 应用程序中的 Frida gadget。House 的目的是通过方便的 web GUI 提供一种简单的方法来原型化 Frida 脚本。

House 的安装说明和操作指南可以在 Readme of the Github repo 中找到。

Magisk

Magisk(“Magic Mask”)是一种 root 你的 Android 设备的方法,它的特殊性在于对系统进行修改的方式。当其他的 root 工具改变系统分区上的实际数据时,Magisk 不会(它被称为“systemless“)。这样就可以隐藏对 root 敏感的应用程序(例如银行或游戏)的修改,并且在 root 的情况下允许使用官方的 Android OTA 升级

你可以通过阅读 GitHub 上的官方文档来熟悉 Magisk。如果您没有安装 Magisk,可以在文档中找到安装说明。如果你使用正式的 Android 版本并计划升级,Magisk 在 GitHub 上提供了一个教程

MobSF

MobSF 是一个自动化的、一体化的移动应用程序审计框架,它也支持 Android APK 文件。启动 MobSF 最简单的方法是通过 Docker。

或通过运行以下命令在主机上本地安装并启动它:

一旦你启动并运行了 MobSF,你就可以通过在浏览器中打开 http://127.0.0.1:80。只要将你想要分析的APK拖放到上传区域,MobSF就会开始工作。

在 MobSF 完成了它的分析之后,你将收到一页关于所有被执行的测试的概述。页面被分割成多个部分,提供了一些关于应用程序攻击表面的初步提示。

显示如下内容:

  • 关于应用程序及其二进制文件的基本信息。

  • 一些选项:
    查看 AndroidManifest.xml 文件。
    查看应用程序的 IPC 组件。

  • 签名者证书。

  • 应用程序的权限。
  • 显示已知缺陷的安全分析,例如是否启用了应用程序备份。
  • 应用程序二进制文件使用的库列表和已解压缩的APK中所有文件的列表。
  • 恶意软件分析,检查恶意网址。

更多细节请参考 MobSF 文档

Objection

Objection 是一个“由 Frida 提供的运行时移动探索工具包”,它的主要目标是允许通过直观的界面在未 root 设备上进行安全性测试。

通过为你提供通过将 Frida gadget 注入应用程序重新打包的工具,Objection 实现了这一目标。通过这种方式,你可以将重新打包的应用程序配置到未 root 设备上让不会与应用程序交互,如前一节所述。

但是,Objection 还提供了一个 REPL,允许你与应用程序交互,使你能够执行应用程序可以执行的任何操作。在项目的主页上可以找到完整的 Objection 功能列表,这里有一些有趣的:

  • 重新打包应用程序来包含 Frida gadget
  • 为常用方法禁用 SSL pinning
  • 访问应用程序存储以下载或上载文件
  • 执行自定义 Frida 脚本
  • 列出活动、服务和广播接收器
  • 开始活动

在未 root 设备上执行高级动态分析的能力是使 Objection 非常有用的特性之一。一个应用程序可能包含高级 RASP 控制,可以检测你的 root 方法,注入 frida-gadget 可能是绕过这些控制的最简单的方法。此外,包含的 Frida 脚本使快速分析应用程序或绕过基本的安全控制变得非常容易。

最后,如果你确实可以访问一个 root 设备,Objection 可以直接连接到运行中的 Frida server 来提供所有功能,而不需要重新打包应用程序。

安装 Objection

正如 Objection's Wiki 中描述的那样,可以通过 pip 直接安装。

如果你的设备已经越狱了,你现在就可以和设备上运行的任何应用程序进行交互了,你可以跳到下面的“使用 Objection”部分。

但是,如果希望在未 root 设备上进行测试,首先需要在应用程序中包含 Frida gadget。 Objection Wiki 详细描述了需要的步骤,但是在做了正确的准备之后,你将能够通过调用 Objection 命令来给 APK 打补丁:

然后,需要使用 adb 安装修改过的应用程序,正如“基本测试操作——安装应用程序”中解释的那样。

使用 Objection

开始使用 Objection 依赖于你是否给 APK 打了补丁,或是你是否使用运行了 Frida-server 的 root 设备。运行一个打过补丁的 APK,Objection 将自动找到附加的设备和搜索一个正在监听的 Frida gadget。但是,在使用 frida-server 时,你需要显式地告诉 frida-server 要分析哪个应用程序。

一旦进入了 Objection REPL,你就可以执行任何可用的命令,以下是一些最有用的方法的概述:

更多关于使用 Objection REPL 的信息可以在 Objection Wiki 上找到。

radare2

radare2(r2)是一个流行的开源逆向工程框架,用于反汇编、调试、打补丁和分析二进制文件,该框架可编写脚本,支持多种架构和文件格式,包括 Android 和 iOS 应用程序。对 Android 支持 Dalvik DEX(odex, multidex),ELF(可执行文件,.so, ART)和 Java(JNI 和 Java 类)。它还包含了几个有用的脚本,可以在移动应用程序分析期间帮助您,它提供了底层的反汇编和安全的静态分析,在传统工具失败时非常有用。

radare2 实现了一个丰富的命令行界面(CLI),你可以在上面执行上述任务。但是,如果你不是很习惯使用 CLI 进行逆向工程,你可以考虑使用 Web UI(通过 -H 参数)或者更方便的 Qt 和 C++ GUI 版本 Cutter。请记住关于 CLI,更具体地说是它的可视化模式和脚本功能(r2pipe),是 radare2 强大功能的核心,绝对值得学习如何使用它。

安装 radare2

请参考 radare2 的官方安装说明。我们强烈建议始终从 GitHub 版本安装 radare2,而不是通过 APT 等常见的包管理器。Radare2正处于非常活跃的开发阶段,这意味着第三方存储库经常会过时。

使用 radare2

radare2 框架包含一组小型实用程序,可以在 r2 shell 中使用,也可以作为独立的 CLI 工具使用。这些工具包括 rabin2rasm2rahash2radiff2rafind2ragg2rarun2rax2,当然还有 r2,这是主要的一个。

例如,你可以使用 rafind2 直接从一个加密的Android 清单文件(AndroidManifest.xml)中读取字符串:

或者使用 rabin2 来获得关于二进制文件的信息:

输入 rabin2 -h 查看所有的选项:

使用 r2 实用程序访问 r2 shell,你可以像加载任何其他二进制一样加载 DEX 二进制文件:

输入 r2 -h 以查看所有可用选项。一个非常常用的参数是 -A,它在加载目标二进制文件后触发分析。但是,应该对小的二进制文件谨慎使用,因为它非常耗费时间和资源。你可以在“Android上的篡改和逆向工程”一章中了解更多。

一旦进入了 r2 shell,你还可以访问其他 radare2 实用程序提供的函数。例如,运行 i 将打印二进制文件的信息,就像 rabin2 -I 所做的那样。

要打印所有字符串,在 r2 shell 中使用 rabin2 -Z 或命令 iz(或更简单的 izq)。

在大多数情况下,你可以在命令中附加特殊选项,例如 q 可以使命令不太冗长,或者 j 可以用 JSON 格式提供输出(使用 〜{} 表示JSON字符串)。

你可以使用 r2 命令 ic 打印类名及其方法。(类信息)

你可以使用 r2 命令 ii 打印导入的方法。(导入信息)

检查二进制文件时,一种常见的方法是搜索、导航到它并使之可视化,以便解释代码。使用 radare2 查找内容的方法之一是使用特定命令过滤输出,即使用 ~ 加上关键字(~+ 表示大小写不敏感)对它们进行 grep。例如,我们可能知道应用程序正在验证一些东西,我们可以检查 radare2 所有的 flag,看看我们在哪里找到与“验证”相关的东西。

当加载一个文件时,radare2 会标记它能够找到的所有东西,这些标记的名称或引用称为 flag,你可以通过命令 f 来访问它们。

在这个案例中,我们使用关键字”verify“来 grep flag。

看起来我们在 0x00000a38 处找到了一个方法(标记了两次),在 0x00001400 处找到了一个字符串。让我们通过使用它的标志来导航(寻找)到那个方法:

当然,您还可以使用 r2 的反汇编功能,并用 pd 命令(或者 pdf,如果你知道你已经位于一个函数中)打印反汇编结果。

r2 命令通常接受选项(见 pd?),例如,你可以通过在命令 pd N 后面附加数字(“N”)来限制显示的操作码。

你可能希望通过输入 V 进入所谓的可视模式,而不是仅仅将反汇编输出到控制台。

默认情况下,你将看到十六进制视图。通过键入 p,你可以切换到不同的视图,如反汇编视图:

Radare2 提供了一种图形模式,它对于跟踪代码流非常有用,你可以在可视模式下输入V

这只是一些 radare2 命令的一部分,用来开始从 Android 二进制文件中获取一些基本信息。Radare2 非常强大,在 Radare2 命令文档中可以找到许多命令。Radare2 将在整个指南中用于不同的目的,如逆向代码、调试或执行二进制分析。我们还将结合使用其他框架,特别是Frida(更多信息,请参阅 r2frida 一节)。

有关 radare2 在Android上的详细使用,特别是在分析原生库时,请参考“Android 上的篡改和逆向工程”一章。你可能还想读一下 radare2 的官方书籍

r2frida

r2frida 是一个允许 radare2 连接 Frida 的项目,有效地将 radare2 强大的逆向工程能力与 Frida 的动态分析工具包结合在一起。R2frida 允许你:

  • 通过 USB 或 TCP 将 radare2 附加到任何本地进程或远程 frida-server。
  • 从目标进程读/写内存。
  • 将映射、符号、导入、类和方法等Frida信息加载到 radare2 中。
  • 从 Frida 调用 r2 命令,因为它将 r2pipe 接口公开到了 Frida Javascript API 中。

安装 r2frida

请参考 r2frida 的官方安装说明

使用 r2frida

随着 frida-server 运行,你现在应该能够使用pid、spawn path、主机和端口或设备 id 连接到它。例如,附加到 PID 1234:

有关如何连接 frida-server 的更多示例,请参阅 r2frida 的 README 页面中的使用部分

连接之后,你应该会看到带有设备id的r2提示符。r2frida 命令必须以 \ 或 =!开始。例如,你可以使用命令 \i 检索目标信息:

要在内存中搜索特定关键字,你可以使用搜索命令 \/

要以 JSON 格式输出搜索结果,只需在前面的搜索命令中添加 j(就像在 r2 shell 中所做的那样)。这可以在大多数命令中使用:

要列出已加载的库,请使用命令 \il 并使用 radare2 的内部 grep 命令  过滤结果。 例如,以下命令将列出与关键字 keystoressl 和 crypto 匹配的已加载库:

类似地,通过特定的关键字列出导出表并过滤结果:

使用命令 db 列出或设置断点。这在分析/修改内存时是有用的:

最后请记住,您也可以使用 \ 运行 Frida JavaScript 代码,加上脚本名称即可:

你可以在他们的 Wiki 项目中找到更多关于如何使用 r2frida 的示例。

基本测试操作

访问设备 Shell

在测试应用程序时,最常见的事情之一就是访问设备 shell。在这一节中,我们将看到如何在有或没有 USB 线情况下从你的主机上远程访问 Android Shell,以及在本地设备访问。

远程 Shell

为了从你的主机连接到 Android 设备的 shell,adb 通常是你选择的工具(除非你喜欢使用远程 SSH 访问,例如通过 Termux)。

对于本节,我们假设你已经正确地启用了开发者模式和 USB 调试,正如“在真实设备上进行测试”中所解释的那样。一旦你通过 USB 连接了 Android 设备,你可以通过运行以下命令访问远程设备的 shell:

按 Control + D 或输入 exit 退出

如果你的设备是 root 的或你正在使用模拟器,一旦你处在远程 shell 中,你可以通过运行 su 获得 root 访问权限:

只有当你使用模拟器时,才可以使用命令 adb root 重新启动 adb,这样下次进入 adb shell 时,你就已经拥有 root 权限了。这也允许在工作站和 Android 文件系统之间双向传输数据,甚至可以访问只有 root 用户可以访问的位置(通过 adb push/pull)。有关数据传输的更多信息,请参阅下面“主机-设备数据传输”一节。

连接多个设备

如果你有不止一个设备,记住在你的所有 adb 命令上包括 -s 参数,后跟设备序列号(例如 adb -s emulator-5554 shell 或 adb -s 00b604081540b7c6 shell)。你可以使用以下命令得到所有连接的设备的列表和他们的序列号:

通过 Wi-Fi 连接一个设备

你也可以不使用 USB 线访问你的 Android 设备。为此,你必须将你的主机和安卓设备连接到同一个 Wi-Fi 网络,然后按照下面的步骤进行:

  • 用 USB 线将设备连接到主机,并设置目标设备在端口5555上监听 TCP/IP 连接:adb tcpip 5555

  • 断开与目标设备的USB连接线,运行 adb connect <device_ip_address>。运行 adb devices 检查该设备现在是否可用。

  • 用 adb shell 打开 shell。

但是请注意,这样做会让你的设备对处于同一网络并知道你的设备 IP 地址的任何人开放。你可能更喜欢使用 USB 连接。

例如,在一个 Nexus 设备上,你可以在设
Settings -> System -> About phone -> Status -> IP address 找到 IP 地址,或者进入 Wi-Fi 菜单,在你连接的网络上点击一下。

在 Android 开发者文档中可以看到完整的说明和注意事项。

通过 SSH 连接设备

如果愿意,还可以启用 SSH 访问。一个方便的选项是使用 Termux,你可以轻松地配置它来提供 SSH 访问(使用密码或公钥身份验证),并使用命令 sshd 启动它(默认在端口8022上启动)。为了通过 SSH 连接到 Termux,只需运行命令 ssh -p 8022 <ip_address>(其中 ip_address 是实际的远程设备IP)。这个选项还有一些额外的好处,它允许在端口8022上通过 SFTP 访问文件系统。

设备上的 Shell 应用

与远程 shell 相比,通常使用设备上的 shell(终端模拟器))可能非常单调乏味,但对于调试,例如网络问题或检查某些配置来说,它很方便。

Termux 是一个用于 Android 的终端模拟器,它提供了一个 Linux 环境,可以直接使用或不使用 root,并且不需要设置。安装额外的包是一项琐碎的任务,但是它有自己的 APT 包管理器(与其他终端模拟器应用程序相比的不同之处)。你可以使用命令 pkg search <pkg_name> 来搜索特定的包,并使用 pkg install <pkg_name> 来安装包。你可以直接从 Google Play 安装 Termux。

主机设备数据传输

使用 adb

你可以使用 adb pull <remote> <local> 和 adb push <local> <remote> 命令将文件复制到设备或从设备中复制。它们的用法非常简单,例如,下面的操作将把 foo.txt 从当前目录(本地)复制到 sdcard 文件夹(远程):

这种方法通常在你知道你想要复制什么和复制到哪里从哪里复制时使用,也支持批量文件传输,例如你可以从 Android 设备复制整个目录到你的工作站。

使用 Android Studio 设备文件浏览器

Android Studio 有一个内置的设备文件资源管理器,你可以通过 View -> Tool Windows -> Device File Explorer 打开它。

如果你使用的是一个 root 设备,那么现在可以开始浏览整个文件系统了。然而,当使用未 root 设备访问应用程序沙箱时,除非应用程序是可调试的,否则不会工作,即使那样,你也会被“监禁”在应用程序沙箱中。

使用 objection

当你在一个特定的应用程序上工作并且想要复制你可能在它的沙箱中遇到的文件时,这个选项非常有用(注意你只能访问目标应用程序能够访问的文件)。这种方法不需要将应用设置为可调试的,在使用 Android Studio 的设备文件浏览器时需要这样做。

首先,如“推荐工具-Objection”中所述,以 Objection 的方式连接到应用程序。然后,像往常一样在终端上使用 ls 和 cd 浏览可用文件:

一旦你有一个文件你想下载,你可以运行 file download <some_file>。这将下载该文件到你的工作目录,同样的方式,你可以使用 file upload 上传文件。

缺点是,在撰写本文时,objection 还不支持批量文件传输,因此你只能复制单个文件。不过,在某些情况下,如果你已经在使用 objection 探索应用程序,并找到了一些有趣的文件,这一点还是很有用的。不需要记下文件的完整路径并使用 adb pull <path_to_some_file>,你可以直接 file download <some_file> 下载文件。

使用 Termux

如果你有一个 root 设备,并且安装了 Termux 以及在其上正确配置了 SSH 访问权限,那么在端口8022上应该已经运行了一个 SFTP(SSH 文件传输协议)服务器,你可从终端访问它:

或者简单地通过使用支持 SFTP 的客户端(如FileZilla):

查看 Termux Wiki 以了解更多有关远程文件访问方法的信息。

获取和解压应用程序
从设备中提取APK文件有几种方法。根据应用程序是公共的还是私有的,你需要决定哪种方法是最简单的。

获取和解压应用程序

从设备中提取 APK 文件有几种方法。你需要根据应用程序是公共的还是私有的,决定哪种方法是最简单的。

其他应用程序商店

最简单的选择之一是从 Google Play Store 的公共应用程序镜像的网站下载 APK。但是,请记住,这些站点不是官方站点,并且不能保证该应用程序没有重新打包或包含恶意程序。一些著名网站托管 APK,并以不修改应用程序而闻名,甚至列出了应用程序的 SHA-1 和 SHA-256 校验和:

注意,你不能控制这些网站,你不能保证他们在未来做什么,在没有其他选择时再使用它们。

使用 gplaycli

gplaycli 是一个基于 Python 的 CLI 工具,用于从 Google Play Store 搜索、安装和更新 Android 应用程序。按照安装步骤操作就可以运行它了,gplaycli提供了几个选项,请参考其帮助(-h)以获得更多信息。

如果你不确定一个应用程序的包名(或 AppID),你可以执行一个关键字搜索 APK (-s):

注意在使用 gplaycli 时的应用区域(
Google Play)限制,为了访问限制在你的国家的应用程序,你可以使用其他应用程序商店,比如“其他应用程序商店”中描述的那些。

接下来,你可以通过指定 APK 的 AppID 来下载(-d)选择的APK(添加 -p 显示进度条,添加 -v 显示信息):

com.google.android.keep.apk 文件将下载到你当前的目录中。正如你所想象的,这种方法是一种非常方便的下载 APK 的的方法,特别是在自动化方面。

你可以使用自己的 Google Play 凭证或令牌,默认情况下,gplaycli 将使用内部提供的令牌

从设备中提取应用程序包

推荐的方法是从设备获取应用程序包包,因为我们可以保证应用程序包没有被第三方修改过。要从一个 root 或非 root 设备获取应用程序,可以使用以下方法:

使用 adb pull 取回 APK,如果你不知道包名,第一步是列出设备上安装的所有应用程序:

一旦找到了应用程序的包名,就需要通过它在系统中存储的完整路径来下载它。

有了 APK 的完整路径后,现在可以简单地使用 adb pull 来提取它。

APK将下载到你的工作目录中。

另外,还有一些像 APK Extractor 这样的应用程序不需要 root,甚至可以通过你喜欢的方法共享所提取的 APK。如果你不喜欢通过网络连接设备或设置 adb 来传输文件,那么这将非常有用。

安装应用程序

使用 adb install 在模拟器或连接的设备上安装 APK。

注意,如果你有原始的源代码并且使用 Android Studio,则不需要这样做,因为 Android Studio 会为你完成应用的打包和安装过程。

信息收集

分析应用程序的一个基本步骤是收集信息,这可以通过检查工作站中的应用程序包或远程访问设备上的应用程序数据来完成。在后面的章节中,你会发现更高级的技术,但现在,我们将集中在基础上:获得所有已安装应用的列表,探索应用程序包,访问设备上的应用程序数据目录。这应该会给你一些关于这个应用程序的背景信息,甚至不需要对它进行逆向工程或执行更高级的分析。我们将回答以下问题:

  • 包中包含哪些文件?
  • 应用程序使用哪些原生库?
  • 应用程序定义哪些应用程序组件?有哪些服务或内容提供者?
  • 应用程序是可调试的吗?
  • 应用程序是否包含网络安全策略?
  • 安装时应用程序是否创建新文件?

列出已安装应用程序

当目标是安装在设备上的应用程序时,你首先要弄清楚你想要分析的应用程序的正确包名。你可以通过 pm(Android软件包管理器)或使用 frida-ps 来检索已安装的应用程序:

你可以添加参数只显示第三方应用程序(-3)和它们的 APK 文件的位置(-f),随后使用 adb pull 可进行下载:

这与运行 adb shell pm path <app_package_id> 类似。

使用frida-ps -Uai获取已连接 USB 设备(-U)上当前安装(-i)的所有应用程序(a):

注意,这也显示了当前正在运行的应用程序的PID。记下标识符和 PID(如果有的话),以后会用到它们。

探索应用程序包

一旦收集了目标应用程序的包名,你将希望开始收集有关它的信息。首先检索 APK,如“基本测试操作——获取和提取应用程序”中所解释的那样。

APK 文件实际上是 ZIP 文件,可以使用一个标准的 unarchiver 进行解压:

下面是被解压的文件:

  • AndroidManifest.xml:包含应用程序包名,目标和最低 API 等级,应用程序配置,应用程序组件,权限等的定义。

  • META-INF:包含应用程序的元数据
    MANIFEST.MF:存储应用程序资源的哈希
    CERT.RSA:应用程序的证书
    CERT.SF:MANIFEST.MF 文件中的资源列表和相应行的 SHA-1 摘要

  • assets:包含应用程序资源(Android 应用程序中使用的文件,例如 XML 文件,JavaScript 文件和图片)的目录,AssetManager 可以检索该目录

  • classes.dex:以 DEX 文件格式编译的类,Dalvik 虚拟机/ Android Runtime 可以运行。 DEX 是 Dalvik 虚拟机的 Java 字节码, 针对小型设备进行了优化

  • lib:包含组成 APK 的第三方库的目录。

  • res:包含尚未编译为 resources.arsc 的资源目录
  • resources.arsc:包含预编译资源的文件,例如用于布局的XML文件

由于使用标准 unzip 工具解压会留下一些不可读的文件,如 AndroidManifest.xml,你最好使用 apktool 解压APK,正如“推荐工具- apktool”中所述,解压结果如下:

 

Android 清单文件

Android 清单文件是信息的主要来源,它包含了很多有趣的信息,比如包名、权限、应用程序组件等等。

这里是一些信息和相应的关键字的非详尽列表,你可以很容易地通过检查文件或使用 grep -i <keyword> AndroidManifest.xml在 AndroidManifest 搜索。

  • App权限:permission(见“Android 平台 API”)

  • Backup llowance:android:allowBackup(见“android上的数据存储”)

  • 应用程序组件:activityservice, providerreceiver(见“Android 平台 API”和“Android 上的数据存储”)

  • 可调试标志:debuggable(参见“Android应用程序的代码质量和编译设置”)

请参阅前面提到的章节来了解更多关于如何测试这些要点的信息。

应用程序二进制文件

如上文“探索应用程序包”所示,应用程序的二进制文件(classes.dex)可以在应用程序包的根目录中找到。它是一个所谓的 DEX(Dalvik 可执行文件)文件,包含编译后的Java代码。由于它的特性,在一些转换之后,你将能够使用反编译器来生成 Java 代码。我们还看到了运行 apktool 后生成的 smali 文件夹。它以一种叫做 smali 的中间语言包含了反汇编的 Dalvik 字节码,这是 Dalvik 可执行文件的一种人类可读的表示。

有关如何 DEX 文件逆向工程的更多信息,请参考“Android上的篡改和逆向工程”一章中的“检查反编译 Java 代码”一节。

原生库

你可以查看 APK 中的 lib 文件夹:

或者使用 objection:

到目前为止,这是你可以获得的关于原生库的所有信息,除非你开始对它们进行逆向工程,即使用不同于逆向应用程序二进制文件的方法来完成,因为代码不能反编译只能反汇编。有关如何对这些库进行逆向工程的更多信息,请参考“Android上的篡改和逆向工程”一章中的“检查原生反汇编代码”一节。

其他的应用程序资源

通常看看 APK 根目录中能否找到其他的资源和文件是很值得的,因为有时它们会包含额外的好东西,如密钥存储库、加密的数据库、证书等。

访问应用程序数据目录

一旦你安装了应用程序,还有更多的信息需要探索,像 objection 就会派上用场了。

当使用 objection 时,你可以检索不同类型的信息,其中 env 将显示应用程序的所有目录信息。

在这些信息我们可以找到:

  • 内部数据目录(又称沙盒目录),在 /data/data/[package-name]或 /data/user/0/[package-name]

  • 外部数据目录在 /storage/emulated/0/Android/data/[package-name] 或 /sdcard/Android/data/[package-name]

  • 应用程序包的路径在 /data/app/

内部数据目录用于存储运行时创建的数据,基本结构如下:

每个文件夹都有自己的目的:

  • cache:此位置用于数据缓存,例如,在此目录中找到 WebView 缓存。

  • code_cache:这是文件系统的应用程序特定缓存目录的位置,设计用于存储缓存代码。对于运行 Android 5.0(API 等级21)或更高版本的设备,当应用或整个平台升级时,系统将删除存储在该位置的所有文件。

  • lib:此文件夹存储用 C/C++ 写的原生库,这些库可能有几个文件扩展名之一,包括 .so 和 .dll(x86支持)。此文件夹包含应用程序具有原生库平台的子目录,包括
    a. armeabi:所有基于 ARM 处理器编译的代码
    b. armeabi-v7a:仅适用于所有基于版本7及更高版本的 ARM 处理器编译的代码
    c. arm64-v8a:所有仅基于版本8及更高版本的 ARM 64位处理器编译的代码
    d. x86:仅适用基于 x86 处理器编译的代码
    e. x86_64:仅适用基于 x86_64 处理器编译的代码
    f. mips:基于 MIPS 处理器编译的代码

  • shared_prefs:此文件夹包含一个XML文件,该文件存储通过 SharedPreferences API 保存的值。

  • file:此文件夹存储应用创建的常规文件。

  • 数据库:此文件夹存储应用程序在运行时生成的 SQLite 数据库文件,例如用户数据文件。

然而,应用程序可能不仅在这些文件夹中存储更多的数据,还会在父文件夹(/data/data/[package-name])中存储更多数据。

有关安全存储敏感数据的更多信息和最佳实践,请参阅“测试数据存储”一章。

监控系统日志

在Android上,你可以通过使用 Logcat 很容易地查看系统消息的日志,Logcat有两种执行方式:

  • Logcat 是 Android Studio 中 Dalvik 调试监视器服务器(DDMS)的一部分。 如果应用程序以调试模式运行,则日志输出将显示在 Android Monitor 的 Logcat 选项卡上。 你可以通过在 Logcat 中定义模式来过滤应用程序的日志输出。

  • 你可以使用 adb 执行 Logcat 来持久化存储日志输出:

使用以下命令,可以在作用域内对应用程序的日志输出进行 grep,只需插入包名。当然,你的应用程序需要运行,以便 ps 能够获得它的 PID。

设置网络测试环境

基本的网络监控/嗅探

通过 tcpdump、netcat (nc) 和 Wireshark 可以实时远程嗅探所有 Android 流量。首先,确保你的手机上有最新版本的 Android tcpdump,以下是安装步骤

如果执行adb root 返回错误adbd cannot run as root in production builds,如下安装 tcpdump:

你可能会遇到错误 mount: '/system' not in /proc/mounts

在这种情况下,你可以使用 $ mount -o rw,remount / 来替代 $ mount -o rw,remount /system; 一行。

记住:要使用 tcpdump,你需要手机有 root 权限。

执行 tcpdump,看看是否有效。一旦传入了一些包,可以按 CTRL+c 停止 tcpdump。

要远程嗅探 Android 手机的网络流量,首先执行 tcpdump 并通过管道将其输出到 netcat(nc):

上面的 tcpdump 命令涉及到

  • 监听wlan0接口,
  • 以字节为单位定义捕获的大小(