• Android 权限


    一、前言
      今天我们来看一下Android中一个众人熟悉的一个属性:shareUserId,关于这个属性可能大家都很熟悉了,最近在开发项目,用到了这个属性,虽然知道一点知识,但是感觉还是有些迷糊,所以就写篇文章来深入研究一下。

      关于Android中的sharedUserId的概念这里就简单介绍一下:

      Android给每个APK进程分配一个单独的空间,manifest中的userid就是对应一个分配的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它)。用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。

      通常,不同的APK会具有不同的userId,因此运行时属于不同的进程中,而不同进程中的资源是不共享的,在保障了程序运行的稳定。然后在有些时候,我们自己开发了多个APK并且需要他们之间互相共享资源,那么就需要通过设置shareUserId来实现这一目的。

      通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样。

    用法也很简单:

      在需要共享资源的项目的每个AndroidMainfest.xml中添加shareuserId的标签。
      android:sharedUserId="com.example"
      id名自由设置,但必须保证每个项目都使用了相同的sharedUserId。一个mainfest只能有一个Shareuserid标签。

    1. SELinux 背景知识

    详细了解 Android 8.0 SELinux,可以参阅 Google 官方文档

    1.1 DAC 与 MAC

    在 SELinux 出现之前,Linux 上的安全模型叫 DAC,全称是 Discretionary Access Control,翻译为自主访问控制。

    DAC 的核心思想很简单,就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以 root 用户启动 Browser,那么 Browser 就有 root 用户的权限,在 Linux 系统上能干任何事情。

    显然,DAD 管理太过宽松,只要想办法在 Android 系统上获取到 root 权限就可以了。那么 SELinux 是怎么解决这个问题呢?在 DAC 之外,它设计了一种新的安全模型,叫 MAC(Mandatory Access Control),翻译为强制访问控制。

    MAC 的理论也很简单,任何进程想在 SELinux 系统上干任何事情,都必须在《安全策略文件》中赋予权限,凡是没有出现在安全策略文件中的权限,就不行。

    关于 DAC 和 MAC,可以总结几个知识点: 

    1. Linux 系统先做 DAC 检查。如果没有通过 DAC 权限检查,则操作直接失败。通过 DAC 检查之后,再做 MAC 权限检查 

    2. SELinux 有自己的一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。

    1.2 SEPolicy 语言

    Linux中有两种东西,一种死的(Inactive),一种活的(Active)。死的东西就是文件(Linux哲学,万物皆文件。注意,万不可狭义解释为File),而活的东西就是进程。此处的 死 和 活 是一种比喻,映射到软件层面的意思是:进程能发起动作,例如它能打开文件并操作它。而文件只能被进程操作。

    下面我们可以直接使用一个例子来看看效果:
    首先我们弄一个插件工程:ShareUserIdPlugin


    这个工程很简单,我们编译安装运行即可。


    在弄一个宿主工程:ShareUserIdHost




    这里有一个核心方法,我们首先通过插件工程的包名:cn.wjdiankong.shareuseridplugin;创建出一个Context对象。
    这里看到第二参数有两个模式:
    Context.CONTEXT_INCLUDE_CODE:这个标志是在我们需要执行插件中的某段代码需要加上的值。
    CONTEXT_IGNORE_SECURITY:这个标志是必须的,是忽视安全性,如果没有这个值的话,那么我们访问什么都是失败的。
    得到了Context变量之后,我们下面就可以通过反射来执行代码和获取资源了,这里需要注意的是,一定要先拿到Context对应的ClassLoader,然后才能加载对应的类,ClassLoader一定是Context的,是插件工程中的类加载器。


    下面我们运行结果看看:


    运行成功了啦~~是不是很简单呢。
    下面如果我们把CONTEXT_INCLUDE_CODE去掉,在运行:


    发现报错了,找不到指定的类。所以如果想运行代码的话,这个值一定要加上。


    我们再把CONTEXT_IGNORE_SECURITY去掉,运行结果:


    看到了,爆出了安全错误,所以要想构造成功Context出来,必须要加上这个值。


    三、步入正题好了,到这里我们就介绍了如何通过包名构造一个Context变量出来,然后执行对应的代码和获取资源。那么这个我们看到工程中貌似没有用到shareUserId这个属性呢?那这个和我们今天要介绍的知识点有什么关系吗?其实没什么关系,上面的例子只能说是做一个简单的引子,那有些同学可能困惑了,为何都没有使用shareUserId属性,这两件事还可以做呢?那岂不是很不安全?其实我们在接触过逆向知识的时候会发现,关于Android中的一个App中的代码和资源说的直白点其实没有安全性可言,比如,我想获取一个一个app中的指定资源,可以使用反编译或者直接解压apk就可以得到,想看到app中的一段代码的含义或者执行结果,反编译也可以做到,所以说这个说的直白点关于代码和资源在Android中其实没什么安全性可说。有办法可以去搞定的。
    当然我们在后面可以用这种构造Context的方式,去实现我们想要的一些功能,比如我们知道了一个app的资源名或者是方法名,想直接在我的工程中用,那么可以使用这种方式就可以啦,不过这个还是很不靠谱的,当然也是一种方式,比如A应用实现了一个很复杂的一个方法,我自己的应用和他没任何关系,但是也需要这个方法,那么可以直接使用这种方式去调用即可。但是前提是A应用安装了。当然正规公司的app都不会这么傻逼的去做的,其实我们在研究逆向app的时候可能会用到哦~~


    那么说了这么多,shareUserId的属性的最大作用是什么呢?
    前面都说了,Android中每个app都对应一个uid,每个uid都有自己的一个沙箱,这是基于安全考虑的,那么说到沙箱,我们会想到的是data/data/XXXX/目录下面的所有数据,因为我们知道这个目录下面的所有数据是一个应用私有的,一般情况下其他应用是没有权限访问的,当然root之后是另外情况,这里就不多说了。这里只看没有root的情况,下面我们在来看一个场景:
    A应用和B应用都是一家公司的,现在想在A应用中能够拿到B引用存储的一些值,那么这时候该怎么办呢?
    这时候就需要用到了shareUserId属性了,但是这里我们在介绍shareUserId属性前,我们先来看一个简单的例子:
    还是使用上面的两个工程:
    ShareUserIdPlugin中的MainActivity.java代码如下:


    这里很简单,我们使用SharedPreferences来存储一个密码,注意模式是:Context.MODE_PRIVATE,关于这里,有很多种模式,后面会详细介绍。


    下面在来看一下宿主工程中的代码,获取密码。


    运行宿主工程结果:


    我们看到运行结果打印出来了几个值,我先不管其他的,看到最后pwd的值是默认值,那说明我们宿主工程中获取插件工程中的密码失败了。
    我们在去看看插件工程中那个shareperference的xml文件的权限:


    这里使用root了之后查看的:-rw-rw----
    关于这个值,不了解的同学可以网上去看一些资料:
    Linux文件权限你分开三段来看:首位代表是目录还是文件,一般不用管,后面的三段每段3位,r代表可读,w代表可写,x代表可执行,第一段是代表文件所属的用户对它的权限,第二段是所属用户组的用户对它的权限,第三段是其他用户对它的权限。第一段:rw- ,所属用户(比如是root)对这个文件可读可写第二段:rw- ,所属用户组用户,对这个文件可读可写第三段:--- ,其他用户对这个文件什么都干不了
    那么从上面的分析可以看出来,这个文件对于其他用户(不同uid的)访问是失败的。所以我们获取密码失败。
    那么这个xml的权限在哪里设置的呢?其实就是在插件工程中的那个创建SharedPreferences的时候:
    其实Context提供了几种模式:
    1、Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆         盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND2、Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。3、Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入


    我们可以查看源码ContextImpl.java:


    这里获取一个SharedPreferencesImpl对象,这个对象是实现了SharedPreferences接口的。这里我们看到采用了缓存机制,将xml的名字和sp对象一一对应起来,所以我们可以得知,一个app中,最好简化xml的个数,尽量将值都定义到一个xml中,减少内存占用。
    我们在看看SharedPreferencesImpl.java类源码:


    有一个全局变量存储了mode值,再看看mMode在哪里用到了:


    在writeToFile这个方法中用到了,这个方法其实后面会分析的,就是SP将内存中的值保存到磁盘中。
    然后再看看ContextImpl的setFilePermissionsFromMode方法:


    好了,到这里,我们可以看到,通过传递进来的mode值,来设置文件的权限。
    那么代码看完了,下面我们在改一下插件工程中的那个创建sp的代码:
    Context.MODE_WORLD_READABLE|Context.MODE_WORLD_WRITEABLE 为读写模式


    再来测试一下:


    看到这里取出来密码了,成功了,关于空指针后面会详细介绍的,这里先不管了。我们再来看一下sp的xml文件权限:


    看到了,其他用户是可以进行读写操作的了,所以取出来的密码是成功的了。
    到这里我们就弄清楚了Context提供的那几个创建sp文件的几种模式的区别,所以我们这里也可以看到,这个模式很重要,对于安全性来说,不过这个默认模式就是private的,也是挺好的。

  • 相关阅读:
    法正(17):玄德
    法正(16):舌战
    法正(15):卢氏
    法正(14):寿星
    struts2笔记---struts2的执行过程
    Oracle数据库(一)
    flask的使用(一)
    struts2--笔记(一)
    docker基础(二)
    docker安装及问题处理
  • 原文地址:https://www.cnblogs.com/jiangzhaowei/p/11712420.html
Copyright © 2020-2023  润新知