0%

【PoRE#0x01】Android APP Reverse PartI

下篇见 这里

上周老师带我们速通了 Android 的 APK 结构、架构以及 Android 系统本身的架构,本人课上走了一会神就再也跟不上了,课后一边 STFW 一边过了一遍 PPT,现在再用这篇笔记过一遍。(可能存在一些错误,希望看到能顺手和俺说下)

本篇笔记分为三个部分,App、System 以及 SDK。

1 Android App

安卓 APP 具有三种形态:

  1. source code
  2. APK(安装包)
  3. App(安装好的软件)
    其中,APK 几乎等价于 App,例如安卓 QQ 就可以直接导出一个软件 APK(虽然有几率出现问题)。课程讲述逆向工程,直接用 APK 的视角来讲述一个安卓软件;但我们这里也可以采取另一种视角 —— 先来看看 source code 阶段的安卓 App 是什么样的。

1.1 source code

在 source code 阶段,App 由 .java 源代码、.xml 数据文件以及别的一些多媒体资源组成。上述的三种文件,大致通过下面的结构被组织起来:

  • manifests 文件夹:存放 AndroidManifest.xml
  • java 文件夹:存放 Java 代码
  • res 文件夹:存放资源文件,如布局相关 xml、多媒体文件
    下面按照上述顺序,介绍他们的用途。

1.1.1 manifest

这一文件在 source code 阶段和 APK 阶段都十分重要。它的作用是全局配置文件,包含了系统运行安卓程序所需要的信息,如 name, version, access rights, referenced library files 等。

它最显著的作用就是对于组件(component) 的基础信息声明
不像平常写的 C、C++软件,Android APP 并不是由函数组成的,也不完全由自己调度其运行顺序(C 程序的函数调用就只是简单的返回地址压栈、修改 rip 而已)。Android APP 由组件组成,并由安卓系统负责调度组件的运行,也就是组件间的调用全部由系统提供的接口来完成。
开发安卓应用就像搭积木,把一个个积木设计好搭在一起就形成了一个 APP。

详细信息:应用清单概览  |  Android 开发者  |  Android Developers

1.1.2 java code (Component)

组件有四种类型:
-Activity
-Service
-Broadcast receiver
-Content provider
它们各自负责了不同的工作。写代码时,通过继承对应组件的类来创建一个组件,比如:
public class MyClass1 extends BroadcastReceiver
就可以创建一个 Broadcast receiver 组件。(当然,还需要在 AndroidManifest.xml 文件中声明其基本信息)

Activity

Activity 是 APP 与用户交互的窗口,也就是 UI 界面。下图是 Activity 的生命周期,开发者通过 @Override(覆盖) Activity 父类的对应名称方法来指示 Activity 组件在这个时候应该干什么。(其他组件也有对应的生命周期)
比如我们想让 Activity 被启动时创建一个界面,就自己写一个 onCreate() 方法,并在其中加入对应代码。系统在创建这个 Activity 时,会调用其 onCreate() 方法,也就是调用我们写的界面创建代码。

Activity Life Cycle

Service

Service 是不在前台运行、不提供界面,在后台工作的组件。我们说 APP 在后台运行,可能就是运行着一些 Service 组件。至于 Service 具体干什么,可以有非常多种。比如在后台接受网络消息、播放音乐等等。

服务可以被启动为两种形态:启动服务(start service)绑定服务(bind service)
启动服务比较独立,自己跑完就结束了;但绑定服务不同。当一个服务被作为绑定服务启动时,它就像一个为另一个进程提供 API 的服务一样。

Service Life Cycle

Broadcast receiver

Broadcast 机制允许应用向外界(所有安装的 APP,甚至是当前不在运行的 APP)广播信息,而该组件就是用来接受这些广播的。安卓系统本身就会发送大量的 broadcast 来指示系统事件的发生。(之后会提到,Intent 类型可以指示广播的内容)

receiver 即可以像别的组件一样在 AndroidManifest.xml 中声明来创建(静态创建),也可以用代码来在应用运行时创建(动态创建),后者需要记得用完了后注销创建的receiver。

Content provider

这个组件用来统一管理一个 APP 所拥有的应用数据,包括文件系统中的、SQLlite 中的、网络上的数据等等。然后提供了一个获取这些数据的接口(毕竟叫做 provider)。

1.1.3 java code(Component Interaction)

安卓中,不同组件并非直接”调用”其他组件,也不会在组件之间建立直接的通信,而是通过安卓系统的系统服务集中地调度和传递消息

这就引出了安卓中一个重要的对象,名为 Intent(意图),可以抽象地描述一个要执行的操作,包括用 startActivity 启动一个 activity,与 broadcastIntent 一起使用以将其发送给任何感兴趣的 BroadcastReceiver 组件,与 Context.startService(Intent)Context.bindService(Intent, Context.BindServiceFlags, Executor, ServiceConnection) 一起使用以与一个后台 Service 通信。

一个 intent 可以包含如下一些域:

  • Component name:想要启动的组件名
  • Action:描述要做什么的字符串
  • Data:目标数据的 URL、类型
  • Category:目标组件的额外信息
  • Extras:额外信息
  • Flags:intent 本身的信息(metadata)

此外,若 intent 用来启动组件,那么它既可以指定某特定组件,也可以指定某一类组件。前者称为 explicit intent,后者称为 implicit intent。

Intent Filter

为了让组件能够接受 implicit intent,可以在 AndroidManifest.xml 中为那个组件声明一个 intent filter,在 filter 描述中指示想要接收的 intent 的信息,如 action、category、data 等信息。
系统会在接收到满足要求的 intent 时启动这个组件。

1.1.4 res

APK 拥有的数据分为静态的 res (主要用来描述视图)和需要使用 AssetManager 来访问的(包括数据文件、数据库等等)。
res 中的资源文件会被映射到 R.java 文件中,可以用 R.id.[resname] 直接引用对应的资源。如 R.id.button1 R.id.textView 等。

一个界面的布局使用 XML 描述,在逻辑上是树状的结构,其每个节点(控件,Widget)或者是包含若干子节点的容器控件(ViewGroup),或者是普通控件(View)。(注意下图那棵树并不是描述界面布局的)

下图展示了一大堆控件类型(每个结点都是一个控件类型),树的父子关系表示继承关系(如 FrameLayout 继承自 View Group)。

Widget

1.2 APK

在课程中,我们会使用 Android Studio 来将项目生成一个 APK 安装包。
APK 大致包含如下一些文件、文件夹:

Name Use
AndroidManifest.xml Compiled global configuration file
classes.dex Compiled and packaged source code
lib Store Binary shared library
META-INF Store the metadata
assets Store the resource file, will not be compiled
res Store the resource file
resource.arsc Compiled and packaged files of res/values

其中,xml 会从原来的文本文件被压缩成 binary xml 格式。
java code 会从原来的源代码编译成 class 文件,多个 class 文件再一起编译打包成一个大 dex 文件(Dalvik 虚拟机字节码,后面会介绍)。

2 Android System

一个完整的 Android System 可以被划分为三个部分:

  • 应用部分:基于核心部分,面向用户;
  • 核心部分:核心功能实现,包括应用框架、原生库(Native Library)等;
  • 底层部分:包括 Linux 操作系统和设备驱动。

虽然我们主要关注的是应用部分——用户安装的软件,但是结合之前的 App 介绍我们可以发现,安卓软件使用了大量由核心部分提供的功能和接口。
如果是用 C 开发程序,程序的运行流一目了然,甚至接近简单的图灵机概念(或者说状态机),就好像整台电脑就只在运行我写的软件、以及底层的操作系统。但开发 Android 程序时,我们无时无刻不在和 Android 核心提供的接口、功能打交道。
因此,有必要大致了解整个 Android 系统的框架,其中了解核心部分为重点。接下来,将会分几节分别简单介绍一下核心部分的各个组件。

2.1 Android Framework

框架层是 Android 系统中最核心的部分,由多个系统服务(System Service)共同组成。最重要的系统服务包括:

  • ActivityManagerService (AMS):组件管理服务
  • PackageManagerService (PMS):包管理服务
  • WindowManagerService (WMS):窗口管理服务
    所有这些系统服务都作为一个线程寄宿在同一个进程中,该进程名为系统核心进程(System Core Process)。应用层的应用时刻都在与它们打交道,每一次构造窗口、处理交互、绘制界面、获得位置信息、了解设备信息等操作,都是在这些服务的支持下进行的。

2.1.1 AMS

AMS 管理着系统上所有组件的生命周期,从启动到终止。具体来说,其主要功能为:

  • 统一调度所有应用的组件
  • 内存管理
  • 进程管理

2.1.2 PMS

PMS 管理着所有软件安装包相关的信息,实现了软件的安装、卸载、升级。其主要功能为:

  • 管理安装软件相关的所有信息
  • 软件授权

2.1.3 WMS

WMS 管理着所有的窗口,包括两个子系统:

  • Layout System:计算窗口信息、管理窗口布局
  • Animation System:基于 layout system 计算得到的信息来渲染动画

2.2 Native Library

原生库包括一系列的二进制动态库,通常是 C/C++开发的(作为共享库,性能十分重要)。这些库并不独立运行,而是作为辅助,帮助 Android Framework 实现了诸多功能,如框架用到的基础算法、资源文件管理模块这些性能关键模块,都通过原生库实现。
由于框架层运行的也是 Java 程序,因此 Android 为所有库提供了 JNI 接口,可以用 Java 调用其中的 C/C++ 写的函数。

此外,为了帮助开发者开发更高效的程序,Android 也允许应用层应用调用原生库进行开发,这一接口被称为 NDK(Native Development Kit)。NDK 包括的库有数学函数库、OpenGL 库等共享库。

2.3 Android Runtime

Java 程序在运行阶段需要二次编译,Android 为它们提供了运行时的支撑。
Dalvik 是一个专为 Android 量身打造的 Java 虚拟机,负责动态解析执行应用、分配空间、管理对象生命周期等工作。其采用一般用于高端设备的基于寄存器的虚拟机架构。

Dalvik 使用的字节码为 .dev 格式,和传统的 Java Bytecode 略有不同,不过 .dev 由其翻译而来。因此,Android 中的程序代码在编译为 .class (Java Bytecode) 后,会再重新整合并翻译为 .dev 字节码。
这个过程会把多个 .class 整合为一个 .dev 文件,但实在不行的时候也会整合出多个 。dev 文件。所以小型的 APK 往往只含有一个 .dev 文件,而大型的 APK 可能会有好几个。

3 Android SDK

Android Software Development Kit,旨在使开发安卓应用更便利,提供了一系列开发工具,包括库、调试器、模拟器、API 文档、代码样例、教程等。
Android 系统版本更新时,提供的 API 可能也会更新,因此 SDK 也划分了版本。每次 Android 更新时,新版本的 SDK 也会提供给开发者。

为了让没有及时更新到新版本的应用也能够运行,应用可以在中声明其 API Level,标识其支持的最低、最高以及目标 SDK 版本。