文章目录
- 前言
- 权限管理
- 系统应用
- 特权应用
- 历史漏洞
- 广播的保护机制
- CVE-2020-0391
- SELinux
前言
在 Android 漏洞挖掘和安全研究过程中,不可避免地会涉及到跟 Android SELinux 安全机制打交道,比如当你手握一个 System 应用的路径穿越的漏洞的时候想去覆写其他应用沙箱的可执行文件的时候,SELinux 极大可能成为你成功路上最大的拦路虎……
通过 ps -efZ
命令可以查看 Android 系统当前运行的进程信息和 SELinux 标签信息:
可以看到 Android 系统在 SELiunx 上对应用标签划分了 系统应用(system_app)、特权应用(priv_app)、平台签名应用(platform_app)、不可信应用(untrusted_app)等。
同时,Android 的权限管理机制又对权限做了以下几类划分(具体可参见Android官方文档):
可以看到,权限管控这里又出现了 system 应用或 privileged 应用才能使用的权限,那么这里的 system 应用或 privileged 应用跟 SELinux 机制的 系统应用(system_app)、特权应用(priv_app)是否有区别?下面逐一来学习下。
【补充】可以使用
adb shell dumpsys package permision |grep -i prot
命令来获取权限等级。
权限管理
先来看下 Android 权限管控机制里的 system 应用或 privileged 应用是如何区分和定义的。
先附上两篇不错的参考文章,以表敬意:
- Android 权限的一些细节;
- “系统应用”与CVE-2020-0391”。
系统应用
第1类 System App:特殊的 Uid
追溯 AOSP 源码,可以看到 Android 对 system app 的判断逻辑为:具有 ApplicationInfo.FLAG_SYSTEM
标记的,被视为 System app。
//frameworks/base/core/java/android/content/pm/ApplicationInfo.java
public boolean isSystemApp() {
return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
而 FLAG_SYSTEM 标记由 PackageManagerService 的构造函数中赋予:
//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.se", SE_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
可以看到:
android.uid.system
android.uid.phone
android.uid.log
android.uid.nfc
android.uid.bluetooth
android.uid.shell
android.uid.se
android.uid.networkstack
android.uid.uwb
等这类具备特殊 shared uid 的 app 都被赋予了 ApplicationInfo.FLAG_SYSTEM 标志,这是第一类 System App。
第2类 System App:特殊的路径
在 InitAppsHelper 下可以看到以下扫描 System app 路径的函数:
//frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
private void scanSystemDirs(PackageParser2 packageParser, ExecutorService executorService) {
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// Collect vendor/product/system_ext overlay packages. (Do this before scanning
// any apps.)
// For security and version matching reason, only consider overlay packages if they
// reside in the right directory.
for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getOverlayFolder() == null) {
continue;
}
scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
scanDirTracedLI(frameworkDir, null,
mSystemParseFlags,
mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
packageParser, executorService);
if (!mPm.mPackages.containsKey("android")) {
throw new IllegalStateException(
"Failed to load frameworks package; check log for warnings");
}
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
final ScanPartition partition = mDirsToScanAsSystem.get(i);
if (partition.getPrivAppFolder() != null) {
scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
mSystemParseFlags,
mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
packageParser, executorService);
}
scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
packageParser, executorService);
}
}
对于安装到 mDirsToScanAsSystem 里面的应用会设置 msystemScanFlags(这个标记仅在 PMS 内部使用,实际上会对应上 ApplicationInfo.FLAG_SYSTEM):
private final int mSystemScanFlags;
static final int SCAN_AS_SYSTEM = 1 << 16;
……
mSystemScanFlags = mScanFlags | SCAN_AS_SYSTEM;
而 mDirsToScanAsSystem 的来源如下:
//frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
final class InitAppsHelper {
private final PackageManagerService mPm;
private final List<ScanPartition> mDirsToScanAsSystem;
……
InitAppsHelper(PackageManagerService pm, ApexManager apexManager,InstallPackageHelper installPackageHelper,
List<ScanPartition> systemPartitions) { //这里的systemPartitions正是下文的scanPartitions.addAll(mSystemPartitions);
mPm = pm;
……
mDirsToScanAsSystem = getSystemScanPartitions();
……
}
……
private List<ScanPartition> getSystemScanPartitions() {
final List<ScanPartition> scanPartitions = new ArrayList<>();
scanPartitions.addAll(mSystemPartitions);
scanPartitions.addAll(getApexScanPartitions());
Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions);
return scanPartitions;
}
不得不说往下追溯 mSystemPartitions 复杂到让人有点犯迷糊了,此处直接放结果吧:
//frameworks/base/core/java/android/content/pm/PackagePartitions.java
/**
* The list of all system partitions that may contain packages in ascending order of
* specificity (the more generic, the earlier in the list a partition appears).
*/
private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
new ArrayList<>(Arrays.asList(
new SystemPartition(Environment.getRootDirectory(),
PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM,
true /* containsPrivApp */, false /* containsOverlay */),
new SystemPartition(Environment.getVendorDirectory(),
PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR,
true /* containsPrivApp */, true /* containsOverlay */),
new SystemPartition(Environment.getOdmDirectory(),
PARTITION_ODM, Partition.PARTITION_NAME_ODM,
true /* containsPrivApp */, true /* containsOverlay */),
new SystemPartition(Environment.getOemDirectory(),
PARTITION_OEM, Partition.PARTITION_NAME_OEM,
false /* containsPrivApp */, true /* containsOverlay */),
new SystemPartition(Environment.getProductDirectory(),
PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT,
true /* containsPrivApp */, true /* containsOverlay */),
new SystemPartition(Environment.getSystemExtDirectory(),
PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT,
true /* containsPrivApp */, true /* containsOverlay */)));
因此可以得出,以下路径下的应用均被 PMS 视为 System App:
/system/framework
/system/app
/system/priv-app
/system_ext/app
/system_ext/priv-app
/system_ext/overlay
/odm/app
/odm/priv-app
/odm/overlay
/oem/app
/oem/overlay
/vendor/app
/vendor/priv-app
/vendor/overlay
/product/app
/product/priv-app
/product/overlay
特权应用
Privileged app,我们称之为 特权 app,主要原因是此类特权 app 可以使用 protectionLevel 为 signatureOrSystem
或 signature|privileged
的权限。实际上上面已经解释过这两个 protectionLevel 的关系,signature|privileged
已替代了 signatureOrSystem
:
//frameworks/base/core/java/android/content/pm/PermissionInfo.java
public static int fixProtectionLevel(int level) {
if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED;
}
……
return level;
}
回归到特权应用的定义:从 ApplicationInfo 的 isPrivilegedApp() 可以看出特权 app 是具有 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
标志的一类 app。
//frameworks/base/core/java/android/content/pm/ApplicationInfo.java
public boolean isPrivilegedApp() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
那么哪些应用会具备 ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
标志?同样有两类。
第1类 Privileged App:特殊的 Uid
前面介绍 System App 的时候已经看到了,PMS 给部分特殊 Shared uid 赋予 System Flag 的同时也赋予了特权 Flag:
//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.se", SE_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
故在 PMS 中,以下 Uid 的应用既属于 System App,又属于 Privileged App:
/system/framework
/system/app
/system/priv-app
/system_ext/app
/system_ext/priv-app
/system_ext/overlay
/odm/app
/odm/priv-app
/odm/overlay
/oem/app
/oem/overlay
/vendor/app
/vendor/priv-app
/vendor/overlay
/product/app
/product/priv-app
/product/overlay
第2类 Privileged App:特殊的路径
不翻源码了,直接看 官方说明文档 吧:
简而言之,以下路径的应用被视为特权应用:
/system/framework
/system/priv-app
/system_ext/priv-app
/odm/priv-app
/vendor/priv-app
/product/priv-app
注意,并非特权应用就可以申请并默认拥有所有 signature|privileged
权限,特权应用想使用某些系统特许权限,需要把白名单添加到 privapp-permissions-platform.xml
文件中(当然也可以单独建立一个文件,例如 com.android.systemui.xml
就是 SystemU I的特权白名单文件)。
关于特权白名单的更多信息也可参考:Android系统开发的特权白名单。
AOSP 包含可根据需要自定义的许可名单实现, 对于包含在 AOSP 中的应用,其权限已在 /etc/permissions/privapp-permissions-platform.xml
中列入许可名单。如果有不应授予的权限,请修改 XML,用 “deny-permission” 标记代替 “permission” 标记。示例:
<!-- This XML file declares which signature|privileged permissions to grant to
privileged apps that come with the platform -->
<permissions>
<privapp-permissions package="com.android.backupconfirm">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.CRYPT_KEEPER"/>
</privapp-permissions>
<privapp-permissions package="com.android.cellbroadcastreceiver">
<!-- Don't allow the application to interact across users -->
<deny-permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.RECEIVE_EMERGENCY_BROADCAST"/>
</privapp-permissions>
...
最后,简单总结下系统应用和特权应用的区分:
- Privileged app 一定也属于 System app,即 System app = Normal System app + Privileged app。
- 当我们说起 system app 时,通常指的是前面提到的特定 uid 和特定目录中的 app,包含了普通的 system app 和 特权 app;
- 而当我们说起有访问 System 权限或者 privileged 权限的 app 时,通常指特权 app;
- 平台签名应用指的是应用的签名和 Android 包一样的应用,和是否是系统应用、特权应用无关。
历史漏洞
要介绍的同样源于 “系统应用”与CVE-2020-0391” 讲述的关于上述 “系统应用” 划分机制出现的历史漏洞。
广播的保护机制
Android 广播机制中有些广播并不是所有应用都可以随意发送的,比如可以在 frameworks/base/core/res/AndroidManifest.xml
看到 AOSP 定义的受保护的广播:
问题1:哪些应用可以发送这些受保护的广播?
看 AMS 的源码 可以知道有 7 个特殊 Uid 的应用和 isPersistent=true 的应用可以发送,否则将抛出安全异常:
问题2:那哪些应用可以定义受保护的广播?
在常规应用想定义受保护的广播是不被允许的:
但是看源码发现 “com.android.providers.telephony” 或 “com.android.systemui” 等应用却可以定义:
引用大佬的分析,ScanPackageUtils.java 文件中的 applyPolicy 函数中 if ((scanFlags & SCAN_AS_SYSTEM) != 0) 对立分支存在注释:“Non system apps cannot mark any broadcast as protected”:
即 system app 无法定义受保护广播,那么反过来就是 system app 能将广播标志为受保护的状态。
CVE-2020-0391
先看下美国国家漏洞库的相关描述:
有点狠,由于未强制执行的受保护广播,可能会以系统身份执行任意命令……
此漏洞于 2020年9月谷歌安全公告 发布,受影响版本仅限于 Android 9 和 Android 10:
Android 9 以前的版本不受影响,那肯定是 “改出来的” 漏洞……
直接看 Google 的 补丁代码:
可以看到,清除保护广播定义的代码跑到了 if ((scanFlags & SCAN_AS_PRIVILEGED) == 0)
的条件下了?
也就是说,只有特权应用能定义保护广播了,普通系统应用并不行。然而从上面的章节可以知道,特权应用的范围比系统应用小很多。那么那些原先由非特权的系统应用定义受保护的广播怎么办?还能生效且受系统保护吗?答案当然是不能……
漏洞修改方案是把错误分支代码改回去:
上文提到该漏洞的影响是任意命令执行,那么是哪个失效的“受保护”广播的杰作?
答案是紧随其后 2020年10月谷歌安全公告 公布的 CVE-2020-11164,但是它归属于闭源组件……
看不到修改的代码,那就引用大佬的分析吧。
漏洞出现在一个名为 Perfdump 的系统应用上,路径是/system/app/Perfdump/Perfdump.apk
,显然这不是一个特权应用,所以它定义的保护广播会失效,那么它定义了哪些没被保护的保护广播呢?其中之一是:
<protected-broadcast android:name="android.perfdump.action.EXT_EXEC_SHELL"/>
一看这名字觉得不得了,不会是一个执行代码的功能吧,猜对了,真的是这样。而且这个应用还是 system uid,也就是说,普通应用可以发一个广播,然后以 system 的身份执行命令。
POC 程序如下:
Intent intent = new Intent("android.perfdump.action.EXT_EXEC_SHELL");
intent.setClassName("com.qualcomm.qti.perfdump", "com.qualcomm.qti.perfdump.StaticReceiver");
intent.putExtra("callerPackageName", "com.test");
intent.putExtra("shellCommand", <command_to_execute>);
sendBroadcast(intent);
SELinux
参考文章:
- SELinux在Android中的应用;
- Android 用户组权限,SELinux心得总结;
- Android 8.1 安全机制 — SEAndroid & SELinux;
- Android 11魔形女系列漏洞分析;
未完待续……先留存个挺清晰的图: