声明
- 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下。
- Android低版本时经常听说Root系统,随着Android版本的升高,提Root的人越来越少了。不过我在系统开发时也有客户提出为系统Root的需求,所以在这里分析下Android系统Root的原理。
- 此篇代码基于LineageOS 14.1(Android 7.1.1),参考一些博客和书籍,不方便逐一列出,仅供学习、知识分享。
1 为何要获取 Root 权限
不同的人 Root 手机的原因各有不同,而我是因为客户需要安装Xposed框架和一些游戏外挂APP,才需要Root的。
2 获得 Root 权限的原理
由于 Android 采用了 Linux内核,也使用 su 命令切换到 root 权限。不过一般我们使用的手机安装的都是release版的 Android 系统,并没有 su 。不过在 Android 源码中已经包含了专门用于 Android 系统的 su 命令,默认的 root密码为空,所以执行该 su 命令后,根本不需要输入任何密码就可以使当前 Shel 拥有 root 权限。
这样看来,现在已经将提取 Android 手机 Root 权限的问题转换为将 su 可执行文件刷入系统 /system/bin 或 /system/xbin 目录并执行 su 命令的问题。
总结的目前的思路有两种:
- 找一个已经有 Root 权限的进程来完成以上步骤:
思路:通过系统漏洞提升权限到 Root。
问题:如何找到 Root的漏洞,目前所找到的洞有哪些?
思路:init 进程启动的服务进程,如 adbd、rild、mtpd、vold等都有 Root权限,找它们的漏洞 - 通过系统之外的某些方法植入:
思路:通过 Recovery 刷机方式刷入 su。
问题:如何刷入Recovery。
3 通过 su 获取 Root 权限的隐患
并不是 Android 系统包含了 su 命令,Android 应用的任何操作都可以在 Root 权限下执行。要想在 Root 权限下执行操作,需要使用 Process 对象来执行命令,并在该进程下执行相应的命令。而这些命令将在 Root 权限下执行,当 su 进程结束后,Root 权限也随之消失。下面的代码描述了如何在 Root 权限下执行 Linux 命令:
//实际上,在创建 Process 对象后,就已经执行 su 命令了,如果这段程序未执行完,su 进程就不会退出
Process process = Runtime.getRuntime().exec("su");
Outputstream os = process.getOutputstream();
//指定要执行的命令,所以这些命令都将拥有 Root 权限
os.write("ls /system/app".getBytes());
//执行 flush 方法以便 Process 对象立刻执行 ls 命令
os.flush();
os.close();
以上代码对用户来说是无感知的,所以如果一个 Android 应用利用 su 执行 Linux 命令,用户是不知道的。执行的这些命令是良性/恶性是不可控的,是个致命的安全隐患。在 Android 系统中的很多操作也和 su 命令类似,有很大的权限,也可能造成危害。
例如,安装APK 时,系统要求执行这些操作时必须提示用户。(弹出一个安装警告窗口,主要告诉用户该 APK 程序拥有哪些权限,并且由用户确定是否继续安装该 APK。尽管这个安装警告窗口并不能阻止 Android系统受到侵害,但至少可以多一道保险,而且这个警告窗口是由系统负责弹出的,不允许取消也就是不允许静默安装)。
基于 Android 系统的这些机制,我们希望APP在请求Root权限时会弹出一个警告窗口来通知用户,并允许用户自己决定是否赋予该应用 Root 权限。要实现这种技术,必须修改 su 命令的源代码,而且还需要编写一个 APK 程序来配合 su 命完成这一功能。通常这个 APK 序叫 Superuser.apk (APK程序用于弹出“Root 授权”警告窗口)。不过 su 命令必须和配套的 Superuser.apk 程序一起使用。
4 应用申请 Root 权限
任何一个应用程序都可以调用 su 命令来获取 Root权限,这样对Android 设备来说是相当不安全的。所以,SuperUser 应用就需要完成以下几个工作来保证 Root 后的设备的安全。
- su 文件是否被替换,保证 su 的安全(替换后的 su文件,估计都动过了手脚 );
- 建立白名单,只允许白名单中的应用使用 Root权限;
- 应用程序调用 su 命令时,向用户弹出 Root 申请Dialog;
系统原生的 su 对于所有的应用程序是平等的,所以原生的su 是无法保证 su 的安全的,SuperUser 必须安装自定义的su,以及能够保证自身 su 不被替换的 deamon 进程。应用请求 Root权限的时候,自定义的 su 则会通知 SuperUser。由SuperUser来进行白名单存储和 Root权限授予提示,让用户选择是否给予该应用 Root权限。而su与SuperUser 之间的通信是靠一个阻塞的 Socket 来完成的。
5 LineageOS 系统为 Root 增加的安全保障
LocalSocket 不同于网络 Socket,后者必须在网络环境中使用,而前者并不需要网络,在使用时会建立一个用于交互数据的设备文件。在传输数据时,会利用该设备文件进行数据交互。对于 su 和 Superuser.apk来说,su是服务端,Superuser.apk 是客户端。当 su 调用 RequestActivity 时,会将用于连接 LocalSocket 服务端的设备文件路径(PATH)传递给 RequestAcitivity(通过am 命令传参),然后 Superuser.apk 利用这个路径建立与 su 的 LocalSocket 连接。su 会利用这个 LocalSocket 连接将调用 su 命令的 Android 应用的信息传递给 Superuser.apk,这些信息就是在Root 授权窗口上看到的 APP 的名称的图标。
su 首次通过 LocalSocket 连接向 Superuser.apk 传递数据时,会将调用 su 命令的 APP 所在的用户 ID 传递给 Superuser.apk,Superuserapk 利用这个 ID 取 Android 应用的 Package,通过 Package 很容易获取 Android 应用的名称和图标。
申请Root 授权的 APP、su 和 Superuser.apk 三者的调用关系如下所示: