安卓平台是个多进程同时运行的系统,它还缺少合适的动态分析接口。因此,在安卓平台上进行全面的动态分析具有高难度和挑战性。已有的研究大多是针对一些安全问题的分析方法或者框架,无法为实现更加灵活、通用的动态分析工具的开发提供支持。此外,很多研究只是针对单进程的分析,在安卓平台多个应用进程协作完成事务的情境下,则无法进行很好的分析。
目录
4 系统实现
4.1 安卓源代码的修改
4.2 安卓系统 ACS 通信服务
4.2.1 初始化功能
4.2.2 注入服务
4.2.3 事件接受和转发
4.3 Dalvik 虚拟机的扩展
4.3.1 ShadowVM 支持
4.3.2 虚拟机事件
4.4 Binder 的扩展
4 系统实现
本章将会详细介绍本文的动态分析框架的各个模块是如何实现的,包括新增的安卓系统 ACS 通信服务、Dalvik 虚拟机的扩展、系统 Binder 库的扩展、以及远程注入和分析服务器的实现。首先,第一部分会先介绍实现这些系统需要涉及的Android 源代码目录,介绍它们原先的作用以及进行了哪些修改;接着第二部分介绍本文在 Android 系统中实现的 ACS 通信服务模块,讲述了如何高效的进行Android 内外部通信;然后在第三部分,通过介绍 DVM 部分的实现,讲述了虚拟机事件的产生以及如何处理系统库共享带来的弊端;在第四部分,介绍了如何扩展 Binder 的中间层以及内核部分,以提供分析需要的 IPC Binder 事件;最后还简单介绍了 Android 系统外对原有ShadowVM 的改造。
4.1 安卓源代码的修改
在介绍这些模块的实现之前,有必要介绍一下安卓源代码的组织结构。安卓源代码是由 repo 工具管理的许多的 git 项目仓库组成的。本文是基于 Android 开源项目的 4.1.1_r6 版本实现的。
表 4-1 列出 Android 源代码根目录下的主要的目录,并依次简介了本文实现需要修改的部分:Dalvik 文件夹含有所有 DVM 实现相关的代码,通过修改 DVM,提供了框架需要的虚拟机事件、注入事件等扩展;而 external 目录则放了 ACS 服务的实现,并编译成动态库为 DVM 提供支持;为了支持 Binder 事件,除了需要修改 frameworks/native/binder 意外,还需要修改内核 kernel 目录,为此需要修改涉及内核的 bionic 目录里的 binder.h 头文件。
4.2 安卓系统 ACS 通信服务
为了能够在系统开始的时候就提供服务,本文通过修改 init.rc 文件,设置了 ACService 的主程序开机自动启动,并向 Service Manager 注册该服务。
ACService 主要承担的职责分为如下几个部分:
1)初始化系统的必要配置。为了能够让系统库在不同的进程中得到不同方式的处理,以减少注入系统库带来的污染,系统允许用户在外部系统分析服务器提供配置。配置中会指明哪些进程需要被监控,并利用 bypass 机制来控制不同进程的行为。因此,启动 ACS 过程中,ACS 需要先与分析服务器通信,获取分析的必要配置。此外,ACS 还需要负责清空不同应用加载的缓存。
2)完成字节码的转发。接收来自 DVM 虚拟机的字节码加载事件,并转发给外部系统的注入服务器进行注入。在注入完成后,接收注入服务器发回的字节码并交还给相应 DVM 以继续完成字节码的加载;
3)完成事件的转发。ACS 需要能够将不同进程的事件发送到分析服务器以推动分析的进行。本文采用了 Ashmem 共享内存的方式,来实现应用进程与 ACS进程的事件传输,以减少内存消耗提高性能。而在 ACS 的主程序中会单独设置一个线程,通过轮询的方法,将不同进程的事件发送出去。
ACService 采用 C++语言开发,代码位置放在外部 库 external 目录下。通过配置安卓的启动进程选项,可以让 ACService 在系统刚开始的时候就自动启动并注册,这样能够在第一时间为第一个 DVM 进程提供服务。
下图显示了在如何向 Service Manager 注册 ACService 服务,以及在程序中如何获取该服务。
/* Register ACSerivce to Service Manager */
void register_service(){
IACService* service = new ACService();
defaultServiceManager()->addService(
String16("ACService"), service);
android::ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
/* Get ACService from Service Manager */
Sp<IACService> get_service(){
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder sm->getService(String16("ACService"));
sp<IACService> interface_cast<IACService>(binder);
}
4.2.1 初始化功能
在 ACService 主程序的 main 方法里,会首先执行清除 DVM 字节码缓存的任务,即删除所有位于/data/dalvik-cache 目录下的 odex 字节码。接着,通过 register_service 向系统注册服务。register_service 以后,主程序会进入循环,等待服务请求。
4.2.2 注入服务
为了实现 2)中的注入功能,ACS 为 DVM 提供了一个接口,在 DVM 加载字节码文件的时候会将字节码以共享内存的方式发送给 ACService,ACService 则通过建立一个网络连接与注入服务器进行通信。DVM 加载字节码的时候,是通过IACService 的 instrumentDex 接口来与 ACService 通信的。instrumentDex 接收四个参数,分别表示含有字节码的文件的名称、原字节码的长度以及存储了原字节码的共享内存文件描述符及其最大容量。每次收到字节码,ACService 会向注入服务器发送新的注入事件。
4.2.3 事件接受和转发
而上述 3)的功能实现分为两个部分:从不同进程获取事件、以及将这些事件发送到分析服务器,实现如图4-3所示。图中上方部分表示进程Process 1,Process2……Process N 分别表示不同的待观测的 DVM 进程(DVM 进程包括了 Zygote 进程、所有从 Zygote fork 生成的进程以及其它利用 dalvikvm 程序启动的进程),各个 DVM 进程在运行的过程中,会产生 Binder IPC 调用事件、Dalvik 虚拟机的事件以及用户的分析事件。
在 ACService 进程中,有一个专门的线程,不断轮询所有队列,将未发送的事件内存块的内容转发出去。这里需要特殊说明的一点是,应用进程从 Zygote fork产生,但不同应用进程与 zygote 并不共享事件发送队列,因而所有进程可以被同等看待。
IACService 还有两个接口 mapPidPname 和 clientClose。mapPidPname 发生在zygote 通过 fork 产生子进程后,用于在 ACService 中维护进程号与进程名的映射关系,并根据进程名来判断该进程是否应当被监听;而 clientClose 表示一个虚拟机进程完成了分析任务或者在虚拟机结束后,需要在 ACS 服务中注销,注销后ACS 服务端能够进一步释放相关资源,提高运行效率。
4.3 Dalvik 虚拟机的扩展
Dalvik 虚拟机的扩展,为本文框架提供了类似 JVMTI 接口中的虚拟机事件,丰富了 ShadowVM 的语意。
4.3.1 ShadowVM 支持
AREDispatch 是本文提供给用户实现异步分析的接口。它提供的 API 如图 3-6所示。这些 API 都是 native 方法,本文将其实现放在 Dalvik 虚拟机中。dalvik/vm/native 目录放置了许多 Java 库中与虚拟机运行时直接相关的类的 native方法实现。AREDispatch 必须作为系统第一批加载的类,因此其 native 方法的实现也放在这个目录下,以保证在系统 JNI 环境准备完毕前,AREDipstach 就可用。
最后通过 ACService 封装的接口 svmNewClassInfo 来将需要的类信息发送到服务器端。
jlong SetAndGetTag(Object* obj){
jlong res;
if(obj == NULL)
res = 0;
else if(obj->tag != 0)
res = obj->tag;
else if(dvmIsClassObject(obj))
res = newClass((ClassObject*)obj);
else {
if(obj->clazz->tag == 0){ //its class not registered
newClass(obj->clazz);
}
obj->tag = set_tag (obj_id++,get_class_id(obj->clazz->tag));
res = obj->tag;
}
return res;
}
jlong newClass(ClassObject *obj){
jlong superid = SetAndGetTag(obj->super);
jlong loaderid = SetAndGetTag(obj->classLoader);
obj->tag = set_tag (obj_id++,clz_id++,1,1);
svmNewClassInfo(obj->tag, obj->descriptor, loaderid, superid);
return obj->tag;
}
4.3.2 虚拟机事件
在 DVM 中可以提供的事件,如下表。
4.4 Binder 的扩展
对 Binder 事件的扩展,允许分析将不同进程、线程之间的事件串联起来,以提供一个更加完整的分析模型。为此通过修改 frameworks 目录下 Binder 实现,以及 service manager 中 C 语言实现的 binder 调用实现了 Binder 事件。
每次 Binder 调用,可以用<client_pid, client_tid, transaction_id>唯一标识,transaction_id 是一个线程局部自增长变量。原来的 Binder 库在被调用端,只能查询调用进程的进程号,而不能查询线程号,为了使 Binder 调用的接收端能够识别出 Binder 调用的完整来历,需要在传输 binder 信息的时候加上额外的信息,包括发起端的线程 id 和事务 id。
struct binder_transaction_data {
...
pid_t sender_pid;
//records the sender’s thread id
pid_t sender_tid;
//records the incremental counter for each binder call
pid_t transaction_id;
uid_t sender_euid;
size_t data_size;
...
};