安卓平台是个多进程同时运行的系统,它还缺少合适的动态分析接口。因此,在安卓平台上进行全面的动态分析具有高难度和挑战性。已有的研究大多是针对一些安全问题的分析方法或者框架,无法为实现更加灵活、通用的动态分析工具的开发提供支持。此外,很多研究只是针对单进程的分析,在安卓平台多个应用进程协作完成事务的情境下,则无法进行很好的分析。
目录
3 系统设计
3.1 系统架构
3.1.1 架构因素
3.1.2 架构设计
3.2 基于事件的分析模型
3.2.1 虚拟机事件
3.2.2 异步分析事件
3.2.3 Binder 事件
3.3 支持并发的事件队列
3.4 分析与注入配置
3.5 本章小结
3 系统设计
分布式动态分析框架是如何设计的。首先,通过介绍该框架的系统架构,来介绍本系统的宏观的分布式结构以及运作的原理;然后介绍本框架基于动态程序分析的编程模型,展示了本系统基于事件驱动的语言特性。
3.1 系统架构
已有的ShadowVM框架,在其上进行了功能的拓展和语义的补充,使得它能够支持 DVM 不同的字节码架构以及安卓平台多进程的运作模型。本文的设计继承了原有研究的高覆盖率和以及高隔离性的优点,能够使得安卓平台上的动态分析开发变得更加简单,功能也更加强大;分布式的分析模型还能够提高分析的功能性并节约移动设备上分析消耗的计算资源;新增的对多进程分析以及Binder IPC 事件的支持也大大提高了分析程序对安卓特有功能的支持,使得对多个进程进行同时分析成为可能。下面会通过介绍本文框架的总体结构来说明系统的主要模块以及功能。接着,通过介绍系统的运作方式来介绍本框架的使用方法。
3.1.1 架构因素
安卓系统是一个资源十分有限的系统。尽管安卓已经利用很多方式来节约匮乏资源(如内存、处理器的使用),比如第二章提出的采用 Zygote 的共享系统库机制、采用 Ashmem 进行内存共享、采用 DVM 寄存器格式的字节码等,资源一直都是安卓平台设计的重要考虑因素。除了上述以外,移动设备的耗电量也是关系用户体验的重要标准,一个尽可能的减少安卓上的高功耗的运算也是重要的考虑点。本文框架,采取的将分析从原程序中抽离出来的方法,在安卓以外的服务器上执行,而应用程序只需要通过往分析服务发送分析关注的事件以及远程异步发送分析需要的数据即可。
3.1.2 架构设计
本文安卓进行了如下设计,图 3-1 是本文动态分析框架的架构:左边的大方框表示的是目标系统,即 Android 操作系统,右边的两个方框分别表示安卓系统以外的注入以及分析服务器。从图中可以看出,安卓采用Linux 作为操作系统。图中展示了本文关心的进程,包括:
1) 分析通信服务(Analysis Communication Service,简称 ACS)。它被用于安卓系统内进程与外部系统的通信服务,包括注入请求以及分析请求;
2) Zygote 进程。它是所有安卓应用程序的父进程,它在安卓系统启动时注入并预加载了安卓系统需要的所有 Java 类库以及 SDK 库;
3) 应用程序进程。它从 Zygote 进程 fork 产生,其程序在程序启动时被注入后加载到内存。
4) System server 进程。它是一个特殊的 DVM 实例,它实现了安卓系统的很多管理功能,比如权限控制、应用调度等。它包含了许多管理器,比如ActivityManager、PackageManager 等,用于进行用户进程的管理。
从图 3-1 可以看到,在安卓外的 JVM 上,用户可以自定义如何进行字节码注入,并在分析端定义响应应用程序的分析逻辑。此外,在安卓系统上,Zygote、System Server 以及所有应用进程都运行在修改过的 DVM 上。
3.2 基于事件的分析模型
用户在分析中可以使用的分析事件包括异步分析分析事件、虚拟机生命周期相关的事件以及进程间通信的 Binder 事件。其中异步分析事件是用户通过注入产生的,而虚拟机以及 Binder 事件则是框架产生的。在分析中用户注入的这些分析事件是以类似远程 RPC 调用的方法传送到服务器端,而对系统产生的事件来说,分析需要实现相应的事件接口来“订阅”。下面就根据这三种事件来介绍本框架的分析模型。
3.2.1 虚拟机事件
虚拟机事件,包括虚拟机的开始、结束,对象的分配、释放,线程的创建、释放等虚拟机内部的事件,一些程序分析可能会需要这些事件来分析一些虚拟机的行为,比如垃圾回收(Garbage Collection,简称 GC)等。DVM 上这些事件并没有被提取出来提供给分析使用。为此,本文通过修改 Dalvik 虚拟机,人工的加入了一些挂钩函数,并针对分析的需要,利用这些挂钩函数,成功的把事件通过ACS 通信服务发送给了远程的分析服务器。
这个 object tag 中包含了对象的 id(一个自增长的整型变量)以及该对象的类型对应的类对象(ClassObject)的标签。从而在分析服务器端,可以为在分析中所有出现的对象维护一个 ShadowObject 的表。
public final class Context {public int processId();public String processName();public Collection <ShadowObject> shadowObjects();public static Collection <Context> contexts();
}
框架还内置支持虚拟机的 fork 事件。前面介绍过,应用程序进程都是通过 Zygote 进程 fork 产生的。针对这个特殊的例子,本框架内置支持 onFork 事件,该事件通过修改 DVM 产生。在 DVM 发生 fork 后,两个 DVM 进程各自开始了自己的新的执行,基于 fork 之前父进程的状态。为此,在分析端也类似,每次fork 事件以后,分析端会把父进程维护的 context 进行一次拷贝,并作为子进程的context。这样在发生 fork 事件以后,在服务器端依然能够精准得维护不同虚拟机的状态。
由于安卓应用始于 fork Zygote 进程,且并不一定会有虚拟机终止事件,因此有时候需要用户通过异步分析接口自定义不同组件开始或终止事件。
interface ObjectFreeListener {void onObjectFree (ShadowObject object, Context ctx);
}
interface ThreadStartListener {void onThreadStart (ShadowThread parent,ShadowThread thread, Context ctx);
}
interface ThreadExitListener {void onThreadExit (ShadowThread thread, Context ctx);
}
interface VmStartListener {void onVmStart (Context parent, Context ctx);}interface VmExitListener {void onVmExit (Context ctx);
}
3.2.2 异步分析事件
在注入端:与一般 RPC 远程调用库类似,都是需要分为两步来进行。首先注册远程分析方法,接着在调用时候指定分析方法并发送对应的参数。下面就一个简单例子,来描述在本文框架下,用户如何定义一个分析的注入部分。
DiSLClass 是用户基于 DiSL 语言写的一个注入配置类。从DiSLClass里可以看到,用户在TargetClass.main方法体的最后注入了对AnalysisRE的 test 方法的调用。(@After 是 DiSL 用于标记方位的注解,marker 属性表明注入的基本元素是方法体,scope 则用来在注入时候匹配方法名称,更多的 DiSL 语言特性,可以参考 DiSL 官方文档)。
public class DiSLClass {@After(marker = BodyMarker.class, scope = "TargetClass.main")public static void test() {AnalysisRE.test (true, (byte) 125, 's', (short) 50000,100000, 10000000000L, 1.5F, 2.5, "Str", Object.class);}
}
AnalysisRE 是用户封装的分析类,它主要的作用是用来注册远程方法,通过调用本文框架提供给用户的 AREDispatch 工具类的 registerMethod 方法,用户可以注册一个远程服务器上的一个分析方法,registerMethod 方法的返回值是这个方法的 id,在调用远程方法时,需要利用这个 id 作为参数以区分不同的分析。
默认同一个进程内不同线程的所有事件会按照发生顺序依次发送。此外,用户定义的分析还可以自定义运行的并发性。通过在 analysisStart 时额外指定一个队列号,从而允许分析事件一定程度的“乱序”。
public class AnalysisRE {static short rpcId = AREDispatch.registerMethod("remote.Analysis.test");public static void test (boolean b, byte by, …, double d,String str, Object obj) {AREDispatch.analysisStart(rpcId);AREDispatch.sendBoolean(b);AREDispatch.sendByte(by);AREDispatch.sendChar ……AREDispatch.sendDouble(d);AREDispatch.sendObjectPlusData(str);AREDispatch.sendObject(obj);AREDispatch.analysisEnd();}
}
需要说明的是,AREDispatch 扩展自原 ShadowVM 的 REDispatch 类,里面包含了一系列用户望在注入中使用的 native 方法,为了实现系统库的全覆盖,所有的类都能访问到该类,框架会将该类注入到系统第一个加载的类库 core.jar 中。在表 3-1 中,列出了 AREDispatch 类提供主要的 API。其中一些接口是为了方便用户调试提供的辅助 API,比如 nativeLog 就是利用安卓 native 层日志系统打印日志的接口。
3.2.3 Binder 事件
框架除了支持 DVM 虚拟机事件,还支持跨进程调用的事件。Binder 为安卓应用提供了强大的 IPC 支持,在应用程序里,用户可以使用基于 Intent 的消息、RPC 调用等构建在 Binder 上的 Java 库来完成跨进程调用。
一次完整的 Binder 调用称为一次 Binder 事务。Binder 事务分为同步和异步两种,异步的 Binder 调用会产生两个事件:客户端发送与服务器端接收。而同步调用,则包含四个事件:客户端发送、服务器接收、服务器端发送返回值、客户端接收返回值。因此框架为分析提供了四个 Binder 事件。如下图所示。
interface RequestSentListener {void onRequestSent(TransactionInfo transaction,NativeThread client, Context ctx);
}
interface RequestReceivedListener {void onRequestReceived(TransactionInfo transaction,NativeThread client, NativeThread server, Context ctx);
}
interface ResponseSentListener {void onResponseSent(TransactionInfo transaction,NativeThread server, Context ctx);
}
interface ResponseReceivedListener {void onResponseReceived(TransactionInfo transaction,NativeThread server, NativeThread client, Context ctx);
}
3.3 支持并发的事件队列
ShadowVM 支持如下的事件顺序配置:
1)全局顺序,即所有事件加入同一个事件队列,在服务器端则按照这唯一的事件队列顺序响应,这种方式是最保守的,所有事件会与 JVM 中的事件按相同顺序还原,虚拟机的事件都会放在这个全局队列中;
2)线程顺序,即分析事件只需要保证线程内有序。在 JVM 执行中,每个线程都有自己的事件队列,而分析服务器在响应这些事件的时候,会为不同的线程分配额外的线程来执行队列中的事务;
3)指定顺序,最灵活的配置方式,用户可以为分析事件指定一个序号,不同序号的事件运行在各自序号对应的队列中。
3.4 分析与注入配置
为了实现类似 DiSL 的动态注入功能,本文修改了 DVM,使得字节码加载到内存之前,会被发送到注入服务器进行注入。由于 DVM 与 JVM 的字节码格式有区别,本文利用开源的库 dex2jar 将 DVM 字节码从 DVM 格式转换成 JVM 格式,再利用 DiSL 进行注入,注入后的 JVM 字节码再由工具转换回 dex 字节码。
3.5 本章小结
通过分析架构因素,解释了这样设计的原因。其后按照分类,依次介绍本框架事件模型包含的三种事件:虚拟机事件、RPC 分析事件以及 Binder 事件。然后,介绍了这些事件是如何组织和实现并发的。