Android安全机制概述

Android安全机制概述

1.Android安全机制概述

Android是一个权限分离的系统。这是利用Linux已有的权限管理机制,通过为没 一个Application分配不同的uid和gid,从而使得不同的Application之间的私有数据和访问(native以及java层通过这种sandbox机制,都可以)达到隔离的目的。与此同时,Android还在此基础上进行扩展,提供了permission机制,它主要是用来对Application可以执行的某些具体操作进行权限细分和访问控制,同时提供了per-URI permission机制,用来提供对某些特定的数据块进行ad-hoc方式的访问。

1.1 uid,gid,gids

Android的权限分离的基础是建立在Linux已有的uid、gid、gids基础上的。

UID。Android在安装一个应用程序,就会为它分配一个uid(参考PackageManagerService中的newUserLP实现)。其中普通Android应用程序的uid是从10000开始分配(参见Process.FIRST_APPLICATION_UID),10000以下是系统进程的uid。

GID。对于普通应用程序来说,gid等于uid。由于每个应用程序的uid和gid都不相同,因此不管是native层还是java层都能够达到保护私有数据的作用。

GIDS。gids是由框架在APplication安装过程中生成,与Application申请的具体权限有关。如果Application申请的相应的permission被granted,而且中有对应的gids,那么这个Application的gids中将包含这个gids。

uid gid gids的详细设置过程:

请参考Act vityManagerService中的startProcessLocked。在通过zygote来启动一个process时,直接将uid传给了gid。再通过zygote来fork出新的进程(zygote.java中的forkAndSpecialize),最终在native层(dalvik_system_zygote.c)中的forkAndSpecializeCommon中通过Linux系统调用来进行gid和uid和gids的设置

1.2permission

一个权限主要包括是三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体权限,例如在COST_MONEY租中包含android.permission.SNED_SMS,android.permission.CALL_PHONE等和费用相关的权限

每个权限通过protectionLevel来标识保护级别:normal,dangerous,signature,signatureorsystem。不同的保护级别代表了程序要使用此权限时的认证方式。normal的权限只要申请了就可以使用;dangerous的权限在安装时需要用户确认才可以使用;signature和signatureorsysten的权限需要使用者的app和系统使用同一个数字证书。

package的权限信息主要通过在AndroidManifest.xml中通过一些标签来指定。如标签,标签等标签。如果package需要申请使用某个权限,那么需要使用标签来指定。

2 Android permission管理机制

2.1 Framework permission机制

2.1.1 安装入口

permission的初始化,是指permission的向系统申请,系统进行检测并授权,并建立相应的数据结构。绝大多数的情况下permission都是从一个package中扫描所得,而这发生在package安装和升级的时候。一般有如下几种安装入口:

n packageinstaller,package被下载安装时会触发使用。packageinstaller会通过APPSecurityPermission来检查dangerous的权限,并对用户给出提示。

n pm命令。

n adb install。最终还是调用pm install来安装apk包

n拷贝及安装。PackageManagerService中使用AppDirObserver对/data/app/进行监视,如果有拷贝即触发安装。

这些安装方式最终都是通过调用PackageManagerService中的函数来完成程序的安装。

2.1.2 permission创建

第一步,从AndroidManifest.xml中提取permission信息。主要提取如下信息:

*shared uid

指定与其他package共享同一个uid。

*permission

提取permissions标签指定属性。它是以permissionInfo来描述一个权限的基本信息。需要指定protectedLevel信息,并指定所属group信息。它将被添加到这个package的permissions这个list结构中。

*permission-tree

提取permissions-tree标签属性。permissions-tree也通过permissionInfo来描述,并被添加到package的permissions这个list结构中。permission-tree只是一个名字空间,用来向其中动态添加一些所谓Dynamic的permission,这些permission可以动态修改。这些permission名称主要以permission-tree的名称开头。它本身不是一种权限,没有protectedLevel和所属group。只是保存了所属package和权限名(带有package前缀的)。

*permission-group

定义permission组信息,用PermissionGroup表示。本身不代表一个权限,会添加进入package的permissionGroups这个list中。

*uses-permission

定义了package需要申请的权限名。将权限名添加到package的requestedPermissions这个list中。

*adopt-permissions

将该标签指定的name存入package的mAdoptPermissions这个list中。Name指定了这个package需要从name指定的package进行权限领养。在system package进行升级时使用。

第二步。获取Package中的证书,验证,并将签名信息保存在package结构中。

1.如果该package来自system img(系统app),那么只需要从该Package的AndroidManifest.xml中获取签名信息,而无需验证其完整性。但是如果这个package与其他package共享一个uid,那么这个共享uid对应的sharedUser中保存的签名与之不一致,那么签名验证失败。

2.如果是普通的package,那么需要提取证书和签名信息,并对文件的完成性进行验证。

第三步。如果是普通的package,那么清除package的mAdoptPermissions字段信息(系统package升级才是用)。

第四步。如果在AndroidManifest.xml中指定了shared user,那么先查看全局list中(mSharedUsers)是否该uid对应的SharedUserSetting数据结构,若没有则新分配一个uid,创建SharedUserSetting并保存到全局list(mSharedUsers)中。

mUserIds保存了系统中已经分配的uid对应的SharedUserSetting结构。每次分配时总是从第一个开始轮询,找到第一个空闲的位置i,然后加上FIRST_APPLICATION_UID即可。

第五步。创建PackageSettings数据结构。并将PackageSettings与SharedUserSetting进行绑定。其中PackageSettings保存了SharedUserSetting结构,而SharedUserSetting中会使用PackageSettings中的签名信息填充自己内部的签名信息,并将PackageSettings添加到一个队列中,表示PackageSettings为其中的共享者之一。

在创建时,首先会以packageName去全局数据结构mPackages中查询是否已经有对应的PackageSettings数据结构存在。如果已经存在PackageSettings数据结构(比如package已经被uninstall,但是还没有删除数据,此时package结构已经被释放)。那么比较该package中的签名信息(从AndroidManifest中扫描得到)与PackageSettings中的签名信息是否匹配。如果不匹配但是为system package,那么信任此package,并将package中的签名信息更新到已有的PackageSettings中去,同时如果这个package与其他package共享了uid,而且shared uid中保存的签名信息与当前package不符,那么签名也验证失败。

第六步。如果mAdoptPermissions字段不为空,那么处理permission的领养(从指定的package对应的PackageSettings中,将权限的拥有者修改为当前package,一般在system app升级的时候才发生,在此之前需要验证当前被领养的package已经被卸载,即检查package数据结构是否存在)。

第七步。添加自定义权限。将package中定义的permissionGroup添加到全局的列表mPermissionGroups中去;将package中定义的permissions添加到全局的列表中去(如果permission-tree类型,那么添加到mSettings.mPermissionTrees;如果是一般的permission添加到mSettings.mPermissions中)。

第八步。清除不一致的permission信息。

1.清除不一致的permission-tree信息。如果该permission-tree的packageSettings字段为空,说明还未对该package进行过解析(若代码执行到此处时PackageSettings肯定已经被创建过),将其remove掉。如果PackageSettings不为空,但是对应的package数据结构为空(说明该package已经被卸载,但数据还有保留),或者package数据结构中根本不含有这个permission-tree,那么将这个permission-tree清除。

2.清除不一致的permission信息。如果PackageSettings或者package结构为空(未解析该package或者被卸载,但数据有保留),或者package中根本没有定义该permission,俺么将改permission清除。

第九步。对每一个package进行轮询,并进行permission授权。

1.对申请的权限进行检查,并更新grantedPermissions列表

2.如果其没有设置shared user id,那么将其gids初始化为mGlobalGids,它从permission.xml中读取。

3.遍历所有申请的权限,进行如下检查

​ 1)如果该权限是normal或者dangerous的。通过检查。

​ 2)如果权限需要签名验证。如果签名验证通过。还需要进行如下检查

​ *如果程序升级,而且是system package。那么是否授予该权限要看原来的package是否被授予了该权

​ 限。如果被授予了,那么通过检查,否则不通过。

​ *如果是新安装的。那么检查通过。

4.如果3中检查通过,那么将这个permission添加到package的grantedPermissions列表中,表示这个permission申请成功(granted)。申请成功的同时会将这个申请到的permission的gids添加到这个package的gids中去。

5.将permissionsFixed字段标准为true,表示这个package的permission进行过修正。后续将禁止对非system的app的权限进行再次修正。

2.1.3Dynamic permission的管理

PackageManagerService提供了addPermission/removePermission接口用来动态添加和删除一些权限。但是这些权限必须是所谓的动态权限(Permission.TYPE_DYNAMIC)。

一个Package如果要添加Dynamic permissions,首先必须要在manifest中声明标签,它实际上是一个权限的名字空间(例如,“com.foo.far"这个权限就是permission-tree”com.foo"的成员),本身不是一个权限。一个Package只能为自己的permission-tree或者拥有相同的uid的package添加或者删除权限。

Package不能通过这种接口去修改在manifest中静态申请的权限,否则抛出异常。

首先查找这个permission在全局permission列表mSetting.mPermissions中是否存在。如果存在,而且类型为BasePermission.TYPE_DYNAMIC那么根据传入的权限信息修改全局表中的权限信息,并触发permissions.xml的持久化。

如果在全局的permission列表mSettings.mPermissions中没有找到,先找到这个permission所在permissionTree,然后添加到全局permission列表mSettings.mPermissions中去,并触发permissions.xml的持久化。

2.1.4 Uri permission的管理

下面两个接口主要用于Uri permission的管理(其实现在ActivityManagerService中)。

//为指定的uid和targetPkg添加对某个content Uri的读或者写权限。

public void grantUriPermission(IApplicationThread caller,String targetPkg,Uri uri,int mode) throws RemoteException;

//清除所有通过grantURIPermission对某个Uri授予的权限。

public void revokeUriPermission(IApplicationThread caller,Uri uri,int mode) throws RemoteException;

grantUriPermission主要的实现过程分析。

grantUriPermission分析:

1.验证caller的ProcessRecord和targetPkg不为空。否则检测不通过。

2.验证所请求的mode为Intent.FLAG_GRANT_READ_URI_PERMISSION或者为Intent.FLAG_GRANT_WRITE_URI_PERMISSION,否则不通过。

3.确保参数Uri是一个content Uri。否则,则验证不通过。

4.通过Uri得到目标ContentProvider,如果不存在,则检测不通过。

5.从PackageManagerService中获得targetPkg对应的uid。

6.检查target uid所对应的package是否真正需要这个权限?

先判断要申请的是读还是写权限,然后查看对应的COntentProvider中对应的readPermission writePermission字段是否保存了权限的名称。如果该字段不为空,则以target uid和该权限名去PackageManagerService中去查找该uid是否被granted了该权限。如果已经获得了该权限,那么无需再去为这个Activity去申请这个Uri权限了,返回。否则继续执行如下操作。

7.检查这个ContentProvider的grantURIPermission开关变量,是否允许对其他package进行权限的grant操作。如果禁止,那么抛出异常。

8.检查这个ContentProvider是否设置了Uri的过滤类型uriPermissionPatterns,如果设置了过滤类型,则将需要申请权限的Uri与之匹配。匹配不通过,则抛出异常。

9.检查调用者自己是否有权限访问这个Uri。如果没有,抛出异常。

10.从mGrantedUriPermissions中取得target uid对应的HashMap数据结构。用target uid和Uri生成UriPermission并保存在mGrantedUriPermissions中。

revokeURIPermission实现分析:

找到该Uri对应的ContentProvider,然后删除mGrantedUriPermissions中与Uri对应的所有权限。

2.2permission的动态检查

这里的动态检查是指是package在程序运行过程中进行某些操作或者数据访问时才进行的check,与之对应的是应用程序安装或者升级时PackageManagerService通过扫描包中的静态权限信息想对应。

系统与权限 检查相关的机制的实现主要集中在PackageManagerService和ActivityManagerService中。ActivityManagerService主要负责的是底层的uid层次的身份检查;PackageManagerService则维护了uid到自己拥有的和被授予的权限的一张表。在通过ActivityManagerService的身份检查后,PackageManagerService根据请求者的uid来查看这张表,判断其是否具有相应的权限。

除此之外,per-URI permission机制的实现也需要一张表,它维护在ActivityManagerService中,它建立了从content URI到被授予访问这个URI的component之间的映射。但是它也需要借助PackageManagerService的机制来辅助实现。

2.2.1framework提供的接口

Android framework中提供了一些接口用来对外来的访问(包括自己)进行权限检查。这些接口主要通过ContextWrapper提供,具体实现在ContextImpl中。如果package接受到外来访问者的操作请求,那么可以调用这些接口进行权限检查。一般情况下可以把这些接口的检查接口分为两种,一种是返回错误,另一种是抛出异常。

主要包含如下几组:

n permission 和 uid 检查API

下面这一组接口主要用来检查某个调用(或者是其他package或者是自己)是否拥有访问某个permission的权限。参数中pid和uid可以指定,如果没有指定,那么framework会通过Binder来获取调用者的uid和pid信息,加以填充。返回值为PackageManager.PERMISSION_GRANTED或者PackageManager.PERMISSION_DENIED。

public int checkPermission(String permission,int pid,int uid)//检查某个uid和pid是否有permission权限

public int checkCallingPermission(String permission)//检查调用者是否有permission权限,如果调用者是自己那么返回PackageManager.PERMISSION_DENIED

public int checkCallingOrSelfPermission(String permission)//检查自己或者其他调用者是否有permission权限

下面这一组和上面类似,如果遇到检查不通过时,会抛出异常,打印消息。

public void enforcePermission(String permission,int pid,int uid,String message)

public void enforceCallingPermission(String permission,String message)

public void enforceCallingOrSelfPermission(String permission,String message)

n per-URI检查API

为某个package添加访问content Uri的读或者写权限。

public void grantUriPermission(String toPackage,Uri uri,int modeFlags)

public void revokeUriPermission(Uri uri,int modeFlags)

检查某个pid和uid的package是否拥有uri的读写权限,返回值表示是否被granted。

public int checkUriPermission(Uri uri,int pid,int uid,int modeFlags)

public int checkCallingUriPermission(Uri uri,int modeFlags)

publiv int checkCallingOrSelfUriPermission(Uri uri,int modeFlags)

public int checkUriPermission(Uri uri,String readPermission,String writePermission,int pid,int uid,int modeFlags)

检查某个pid和uid的package是否拥有uri的读写权限,如果失败则抛出异常,打印消息。

public void enforceUriPermission(Uri uri,int pid,int uid,int modeFlags,String message)

public void enforceCallingUriPermission(Uri uri,int modeFlags,String message)

public void enforceCallingOrSelfUriPermission(Uri uri,int modeFlags,String message)

public void enforceUriPermission(Uri uri,String readPermission,String writePermission,int pid,int uid,int modeFlags,String message)

2.2.2实现分析

ContextImpl.java中提供的API,其实都是由ActivityManagerService中的如下几个接口进行的封装。

public int checkPermission(String permission,int pid,int uid) throws RemoteException;//主要用于一般的permission检查

public int checkUriPermission(Uri uri,int pid,int uid,int mode) throws RemoteException;//主要用于Content Uri的permission检查

n checkPermission的实现分析

1.如果传入的permission名称为null,那么返回PackageManager.PERMISSION_DENIED。

2.判断调用者uid是否符合要求。

​ 1)如果uid为0,说明root权限的进程,对权限不作控制。

​ 2)如果uid为system server进程的uid,说明system server,对权限不作控制。

​ 3)如果是ActivityManager进程本身,对权限不作控制。

​ 4)如果调用者uid与参数传入的req uid不一致,那么返回PackageManager.PERMISSION_DENIED。

3.如果通过2的检查后,再调用PackageManagerService.checkUidPermission,判断这个uid是否拥有相应的权限,分析如下。

​ 1)首先它通过调用getUserIdLP,去PackageManagerService.Setting.mUserIds数组中,根据uid查找uid(也就是package)的权限列表。一旦找到,就表示有相应的权限。

​ 2)如果没有找到,那么再去PackageManagerService.mSystemPermissions中找。这些信息是启动时,从/system/etc/permissions/platform.xml中读取的。这里记录了一些系统级的应用的uid对应的permission。

​ 3)返回结果

n同样checkUriPermission的实现主要在ActivityManagerService中,分析如下:

1.如果uid为0,说明是root用户,那么不控制权限。

2.否则,在ActivityManagerService维护的mGrantedUriPermissions这个表中查找这个uid是否含有这个权限,如果有再检查其请求的是读还是写权限。

3.Android签名机制。

关于签名机制,其实分两个阶段。

包扫描阶段需要进行完整性和证书的验证。普通package的签名和证书是必须要先经过验证的。具体做法是对manifest下面的几个文件进行完整性检查。完整性检查包括这个jar包中的所有文件。如果是系统package的话,只需要使用AndroidManifest.xml这个文件去提取签名和验证信息就可以了。

在权限创建阶段。如果该package来自system img(系统app),那么trust it,而且使用新的签名信息去替换旧的信息。前提是如果这个package与其他package共享一个uid,那么这个共享uid对应的sharedUser中保存的签名与之不一致,那么签名验证失败。有些时候系统卸载一个app,但是不删除数据,那么其PackageSettings信息会保留,其中会保存签名信息。这样再安装时就会出现不一致。

3.1Android Package签名原理

android中系统和app都是需要签名的。可以自己通过development/tools/make_key来生成公钥和私钥。

android源代码中提供了工具./out/host/linux-x86/framework/signapk.jar来进行手动签名。签名的主要作用在于限制对于程序的修改权限仅限于同一来源。系统中主要有两个地方会检查。如果是程序升级的安装,则要检查新旧程序的签名证书是否一致,如果不一致则会安装失败;对于申请权限的protectedlevel为signature或者signatureorsystem的,会检查权限申请者和权限声明者的证书是否是一致的。签名相关文件可以从apk包中的META-INF目录下找到。

signapk.jar的源代码在build/tools/signapk,签名主要有以下几步:

1.将除去CERT.RSA,CERT.SF,MANIFEST.MF的所有文件生成SHA1签名

首先将除了CERT.RSA,CERT.SF,MANIFEST.MF之外的所有非目录文件分别用SHA-1计算摘要信息,然后使用base64进行编码,存入MANIFEST.MF中。如果MANIFEST.MF不存在,则需要创建。存放格式是entry name以及对应的摘要

2.根据之前计算的SHA1摘要信息,以及私钥生成一系列的signature并写入CERT.SF

对整个MANIFEST.MF进行SHA1计算,并将摘要信息存入CERT.SF中。然后对之前计算的所有摘要信息使用SHA1再次计算数字签名,并写入CERT.SF中

3.把公钥和签名信息写入CERT.RST

把之前整个的签名输出文件使用私有秘钥计算签名。同时将签名结果,以及之前声称的公钥信息写入CERT.RSA中保存。

3.2Package的签名验证

安装时对一个Package的签名验证的主要逻辑在JarVerifier.java文件的verifyCertificate函数中实现。其主要的思路是通过提取cert.rsa中的证书和签名信息,获取签名算法等信息,然后按照之前对apk签名的方法进行计算,比较得到的签名和摘要信息与apk中保存的匹配。

第一步。提取证书信息,并对cert.sr进行完整性验证。

1.先找到是否有DSA和RSA文件,如果找到则对其进行decode,然后读取其中的所有的证书列表(这些证书会被保存在Package信息中,供后续使用)。

2.读取这个文件中的签名数据信息块列表,只取第一个签名数据块。读取其中的发布者和证书序列号。

3.根据证书序列号,去匹配之前得到的所有证书,找到与之匹配的证书。

4.从之前得到的签名数据块中读取签名算法和编码方式等信息

5.读取cert.sf文件,并计算整个的签名,与数据块中的签名(编码格式的)进行比较,如果相同则完整性校验成功。

第二步。使用cert.sf中的摘要信息,验证MANIFEST.MF的完整性。

在cert.sf中提取SHA1-Digest-Manifest或者SHA1-Digest开头的签名数据块(-Digest-Manifest这个是整个MANIFEST.MF的摘要信息,其它的是jar包中其他文件的摘要信息),并逐个对这些数据块进行验证。验证的方法是,先将cert.sf看做是很多的entries,每个entries包含了一些基本信息,如这个entry中使用的摘要算法(SHA1等),对jar包中的哪个文件计算了摘要,摘要结果是什么。处理时先找到每个摘要数据块中的文件信息,然后从jar包中读取,然后使用-Digest之前的摘要算法进行计算,如果计算结果与摘要数据块中保存的信息相匹配,那么就完成验证。

你可能感兴趣的:(Android)