目录
  1. 1. 一、权限分级体系
    1. 1.1. 一.1 权限保护级别的完整定义
    2. 1.2. 一.2 权限检查的双层架构
    3. 1.3. 一.3 UID/GID 分配机制
  2. 2. 二、如何分析应用的权限使用
    1. 2.1. 二.1 使用 aapt 分析权限声明
    2. 2.2. 二.2 使用 JADX 分析权限的实际使用
    3. 2.3. 二.3 使用 smali 分析权限检查逻辑
    4. 2.4. 二.4 运行时权限分析工具
    5. 2.5. 二.5 Frida 动态 Hook 权限检查
  3. 3. 三、组件导出与权限保护
    1. 3.1. 三.1 四大组件的权限保护机制
    2. 3.2. 三.2 组件权限检查源码分析
    3. 3.3. 三.3 bypass 导出组件的攻击思路
    4. 3.4. 三.4 使用 drozer 自动化分析组件权限安全
  4. 4. 四、SELinux 与 Android 权限
    1. 4.1. 四.1 SELinux 策略如何限制权限
    2. 4.2. 四.2 AOSP 中系统权限定义的源码剖析
    3. 4.3. 四.3 权限检查的完整调用链
    4. 4.4. 四.4 权限提升漏洞案例分析
  5. 5. 五、最小权限原则在实践中的应用
    1. 5.1. 五.1 权限审计清单
    2. 5.2. 五.2 使用代码扫描工具
    3. 5.3. 五.3 AppOps 权限管理框架
  6. 6. 六、AOSP 权限相关关键源码
  7. 7. 面试常考问题
【逆向安全技术-防护篇】常用权限分析

一、权限分级体系

Android 权限分为三个保护级别:Normal(普通权限,安装时自动授予)、Dangerous(危险权限,需运行时动态申请)、Signature(签名权限,仅与系统同签名的应用可获取)。权限声明在 AndroidManifest.xml 中,但声明不等于获得——系统会根据保护级别分派处理逻辑。

从实现原理上看,权限检查分为两层:Framework 层通过 PackageManagerService 管理权限注册与检查,调用路径为 PackageManager.checkPermission()ActivityManagerService.checkComponentPermission()Kernel 层则基于 Linux DAC(Discretionary Access Control,自主访问控制)和 MAC(Mandatory Access Control,强制访问控制,即 SELinux),通过 UID/GID 映射实现进程级隔离。

一.1 权限保护级别的完整定义

除了常见的三类,Android 还有更多保护级别:

Protection Level 完整分类:

Normal (0x0)
├── 普通权限,安装时自动授予
├── 不会弹出确认对话框
└── 示例: INTERNET, ACCESS_NETWORK_STATE, BLUETOOTH, WAKE_LOCK

Dangerous (0x1)
├── 危险权限,涉及用户隐私数据
├── Android 6.0+ 需运行时动态申请
├── 按权限组(Permission Group)归类
└── 示例: CAMERA, CONTACTS, LOCATION, SMS, STORAGE

Signature (0x2)
├── 签名级别,仅与声明该权限的系统应用同签名的 App 可获取
├── 安装时检查签名匹配,不匹配则静默拒绝
└── 示例: BIND_DEVICE_ADMIN, BIND_NOTIFICATION_LISTENER_SERVICE

SignatureOrSystem (0x3)
├── 签名或系统级:同签名 OR 安装在 /system 分区
├── 已废弃(API 23),被拆分为 Signature|Privileged
└── 示例: WRITE_SECURE_SETTINGS, CHANGE_CONFIGURATION

Internal (0x4)
├── 仅由系统使用
└── 第三方应用无法获取

Privileged (0x10, 可组合)
├── 特权级别,必须系统应用且位于 /system/priv-app/
├── Android 8.0+ 引入
└── 示例: INSTALL_PACKAGES, DELETE_PACKAGES

Development (0x20, 可组合)
├── 开发者选项启用后可用
└── 不引入安全风险

Appop (0x40, 可组合)
├── 与 AppOps 框架配合使用
└── 更细粒度的权限控制

Instant (0x100, 可组合)
├── Instant App 可被授予
└── Android 8.0+

RetailDemo (0x200, 可组合)
├── 零售演示模式
└── Android 9.0+

组合示例:
<permission
android:protectionLevel="signature|privileged" />

一.2 权限检查的双层架构

┌─────────────────────────────────────────────────────────┐
│ Framework 层 (Java) │
│ │
│ Context.checkPermission(perm, pid, uid) │
│ └→ ActivityManager.checkPermission(perm, pid, uid) │
│ └→ ActivityManagerService.checkPermission(...) │
│ └→ PermissionManagerService.checkPermission() │
│ ├── 检查 uid 是否声明了该权限 │
│ ├── 检查 protectionLevel 匹配 │
│ ├── 检查 AppOps 状态(动态权限) │
│ └── 返回 PERMISSION_GRANTED / DENIED │
│ │
│ 关键源文件: │
│ /frameworks/base/core/java/android/app/ │
│ ApplicationPackageManager.java │
│ /frameworks/base/services/core/java/com/android/server/│
│ pm/permission/PermissionManagerService.java │
│ /frameworks/base/core/java/android/content/ │
│ ContextImpl.java │
└───────────────────────┬─────────────────────────────────┘
│ UID/GID 映射
┌───────────────────────▼─────────────────────────────────┐
│ Kernel 层 (C) │
│ │
│ Linux DAC (Discretionary Access Control) │
│ └── 基于 UID/GID 的文件/进程访问控制 │
│ ├── 每个 App 分配唯一 UID (Linux 10000+) │
│ ├── 每个权限可能对应一个 GID (如 inet → 3003) │
│ └── 文件系统权限: rwx 针对 owner/group/other │
│ │
│ SELinux MAC (Mandatory Access Control) │
│ └── 基于安全策略的强制访问控制 │
│ ├── 进程运行在 SELinux domain 中 (如 untrusted_app) │
│ ├── 每个资源有 SELinux label (如 app_data_file) │
│ ├── 策略文件: /sepolicy/ 下的 *.te 文件 │
│ └── 即使在 DAC 通过的操作,SELinux 也能拒绝 │
│ │
│ 关键源文件: │
│ /kernel/security/selinux/ │
│ /system/sepolicy/ │
└─────────────────────────────────────────────────────────┘

一.3 UID/GID 分配机制

Android 中每个应用分配唯一的 Linux UID(从 10000 开始递增)。系统权限通过 GID 映射:

// system/core/libcutils/fs_config.cpp 中的权限-GID 映射(简化)
static const struct android_id_info android_ids[] = {
{ "root", AID_ROOT, }, // 0
{ "system", AID_SYSTEM, }, // 1000
{ "radio", AID_RADIO, }, // 1001
{ "bluetooth", AID_BLUETOOTH, }, // 1002
{ "graphics", AID_GRAPHICS, }, // 1003
{ "input", AID_INPUT, }, // 1004
{ "audio", AID_AUDIO, }, // 1005
{ "camera", AID_CAMERA, }, // 1006
{ "log", AID_LOG, }, // 1007
{ "compass", AID_COMPASS, }, // 1008
{ "mount", AID_MOUNT, }, // 1009
{ "wifi", AID_WIFI, }, // 1010
{ "adb", AID_ADB, }, // 1011
{ "install", AID_INSTALL, }, // 1012
{ "media", AID_MEDIA, }, // 1013
{ "dhcp", AID_DHCP, }, // 1014
{ "sdcard_rw", AID_SDCARD_RW, }, // 1015
{ "vpn", AID_VPN, }, // 1016
{ "keystore", AID_KEYSTORE, }, // 1017
{ "usb", AID_USB, }, // 1018
{ "drm", AID_DRM, }, // 1019
{ "mdnsr", AID_MDNSR, }, // 1020
{ "gps", AID_GPS, }, // 1021
// ... 更多 GID
{ "inet", AID_INET, }, // 3003 (INTERNET 权限)
{ "net_raw", AID_NET_RAW, }, // 3004
{ "net_admin", AID_NET_ADMIN, }, // 3005
{ "net_bw_stats", AID_NET_BW_STATS}, // 3006
{ "net_bw_acct", AID_NET_BW_ACCT }, // 3007
// ...
{ "everybody", AID_EVERYBODY, }, // 9997
{ "misc", AID_MISC, }, // 9998
{ "nobody", AID_NOBODY, }, // 9999
{ "app", AID_APP, }, // 10000 (应用 UID 起点)
};

当一个应用声明了 INTERNET 权限,系统会在创建应用进程时将其加入 inet (GID 3003) 组,从而使该进程有权访问网络 socket。

二、如何分析应用的权限使用

二.1 使用 aapt 分析权限声明

# 列出应用声明的所有权限
aapt dump permissions app.apk

# 示例输出:
# package: com.example.app
# uses-permission: name='android.permission.INTERNET'
# uses-permission: name='android.permission.ACCESS_FINE_LOCATION'
# uses-permission: name='android.permission.CAMERA'
# uses-permission: name='android.permission.READ_CONTACTS'
# uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'

# 列出 APK 的完整信息(包括权限)
aapt dump badging app.apk | grep "uses-permission"

# 使用更详细的 dump
aapt d --values permissions app.apk

二.2 使用 JADX 分析权限的实际使用

# 在反编译代码中搜索权限相关的字符串
grep -r "checkPermission" jadx_output/
grep -r "checkCallingPermission" jadx_output/
grep -r "enforcePermission" jadx_output/
grep -r "requestPermissions" jadx_output/
grep -r "shouldShowRequestPermissionRationale" jadx_output/

二.3 使用 smali 分析权限检查逻辑

权限相关的 smali 代码模式识别:

# 权限检查模式 1: checkSelfPermission
invoke-virtual {p0}, ..., Landroid/content/Context;->checkSelfPermission(
Ljava/lang/String;)I

move-result v0
if-eqz v0, :permission_granted
# 权限被拒绝的处理逻辑

# 权限检查模式 2: checkCallingPermission
invoke-virtual {p0, p1}, ..., Landroid/content/pm/PackageManager;->checkPermission(
Ljava/lang/String; Ljava/lang/String;)I

# 权限请求模式: requestPermissions
const/4 v0, 0x1
new-array v0, v0, [Ljava/lang/String;
const-string v1, "android.permission.CAMERA"
aput-object v1, v0, 0
invoke-virtual {p0, v0, v2}, ...,
Landroid/app/Activity;->requestPermissions([Ljava/lang/String;I)V

二.4 运行时权限分析工具

# 使用 dumpsys 查看应用的权限授权状态
adb shell dumpsys package com.target.app | grep -A 50 "requested permissions"

# 示例输出:
# requested permissions:
# android.permission.INTERNET: granted=true
# android.permission.ACCESS_FINE_LOCATION: granted=false
# android.permission.CAMERA: granted=true

# 查看应用当前运行的进程
adb shell ps -A | grep com.target.app
# 查看进程的 UID/GID
adb shell cat /proc/<pid>/status | grep -E "Uid|Gid"

# 动态监控权限请求(需要 root)
adb shell dumpsys activity permissions

二.5 Frida 动态 Hook 权限检查

// Hook 所有权限检查方法
Java.perform(function() {
var ContextImpl = Java.use("android.app.ContextImpl");

ContextImpl.checkPermission.implementation = function(perm, pid, uid) {
var result = this.checkPermission(perm, pid, uid);
if (result === 0) {
console.log("[+] Permission GRANTED: " + perm
+ " (pid=" + pid + ", uid=" + uid + ")");
} else {
console.log("[-] Permission DENIED: " + perm
+ " (pid=" + pid + ", uid=" + uid + ")");
}
return result;
};

// Hook Activity 的权限请求
var Activity = Java.use("android.app.Activity");
Activity.requestPermissions.implementation = function(permissions, reqCode) {
console.log("[*] requestPermissions called:");
for (var i = 0; i < permissions.length; i++) {
console.log(" " + permissions[i]);
}
return this.requestPermissions(permissions, reqCode);
};
});

三、组件导出与权限保护

三.1 四大组件的权限保护机制

Android 四大组件(Activity、Service、BroadcastReceiver、ContentProvider)都可以通过 android:permission 属性进行权限保护:

<activity
android:name=".AdminActivity"
android:permission="com.example.app.ADMIN_ACCESS"
android:exported="true">
<intent-filter>
<action android:name="com.example.action.ADMIN" />
</intent-filter>
</activity>

<service
android:name=".DataSyncService"
android:permission="com.example.app.SYNC_CONTROL"
android:exported="true" />

<receiver
android:name=".BootReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

<provider
android:name=".DataProvider"
android:permission="com.example.app.DATA_ACCESS"
android:exported="true"
android:authorities="com.example.app.provider" />

三.2 组件权限检查源码分析

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
// checkComponentPermission() 的简化逻辑

int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// 1. 系统 UID(System)和 root 直接通过
if (uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) {
return PackageManager.PERMISSION_GRANTED;
}

// 2. 隔离的进程(IsolatedProcess)没有权限
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}

// 3. 调用者和组件拥有者同一 UID → 直接通过(自调用)
if (UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}

// 4. 如果组件未导出(exported=false),拒绝外部访问
if (!exported) {
return PackageManager.PERMISSION_DENIED;
}

// 5. 如果组件不需要权限 → 放行
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}

// 6. 检查调用者是否持有该权限
return checkPermission(permission, ...);
}

三.3 bypass 导出组件的攻击思路

// 攻击场景 1:调用无权限保护的导出 Activity
Intent intent = new Intent();
intent.setClassName("com.victim.app", "com.victim.app.AdminActivity");
// 如果 AdminActivity 没有设置 android:permission,外部可以启动
startActivity(intent);

// 攻击场景 2:调用无权限保护的 ContentProvider
Cursor cursor = getContentResolver().query(
Uri.parse("content://com.victim.app.provider/users"),
null, null, null, null);
// 可能泄露用户数据

// 攻击场景 3:发送广播给无权限保护的 Receiver
Intent intent = new Intent("com.victim.app.SECRET_ACTION");
intent.putExtra("data", "malicious_payload");
sendBroadcast(intent);
// 可能触发非预期的内部逻辑

三.4 使用 drozer 自动化分析组件权限安全

# drozer (https://github.com/WithSecureLabs/drozer) 的组件安全检查

# 列出所有导出组件及其权限
dz> run app.package.attacksurface com.target.app

# 检查 ContentProvider 访问控制
dz> run app.provider.info -a com.target.app

# 尝试读取 ContentProvider
dz> run app.provider.query content://com.target.app.provider/users

# 检查 Activity 启动权限
dz> run app.activity.info -a com.target.app

# 检查 BroadcastReceiver
dz> run app.broadcast.info -a com.target.app

# 自动化漏洞扫描
dz> run scanner.provider.injection -a com.target.app
dz> run scanner.provider.traversal -a com.target.app

四、SELinux 与 Android 权限

四.1 SELinux 策略如何限制权限

Android 5.0+ 默认启用 SELinux Enforcing 模式。每个进程运行在预定义的 SELinux domain 中:

SELinux 相关核心概念:

Type (类型标签)
├── 进程标签:u:r:untrusted_app:s0
├── 文件标签:u:object_r:app_data_file:s0
└── 套接字标签:u:object_r:netlink_socket:s0

Domain (安全域)
├── untrusted_app:所有第三方应用
├── platform_app:系统平台应用
├── system_server:系统服务进程
├── zygote:Zygote 进程
├── init:init 进程
└── kernel:内核进程

策略规则(Policy Rule):
allow <source_domain> <target_type>:<class> <permissions>;

示例:
allow untrusted_app app_data_file:file { read write open };
allow system_server netlink_socket:socket create;

四.2 AOSP 中系统权限定义的源码剖析

<!-- frameworks/base/core/res/AndroidManifest.xml -->
<!-- 系统权限的权威定义文件 -->

<!-- Dangerous 权限示例 -->
<permission android:name="android.permission.CAMERA"
android:permissionGroup="android.permission-group.CAMERA"
android:protectionLevel="dangerous"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera" />

<!-- Signature 权限示例 -->
<permission android:name="android.permission.BIND_DEVICE_ADMIN"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="signature"
android:label="@string/permlab_bindDeviceAdmin"
android:description="@string/permdesc_bindDeviceAdmin" />

<!-- 系统级权限示例 -->
<permission android:name="android.permission.INSTALL_PACKAGES"
android:protectionLevel="signature|privileged"
android:label="@string/permlab_installPackages"
android:description="@string/permdesc_installPackages" />

四.3 权限检查的完整调用链

应用调用 checkPermission(permission, pid, uid)

├─ ContextImpl.checkPermission(permission, pid, uid)
│ frameworks/base/core/java/android/app/ContextImpl.java

├─ ActivityManager.getService().checkPermission(...)
│ Binder IPC 调用到 system_server

├─ ActivityManagerService.checkPermission(...)
│ frameworks/base/services/core/java/.../ActivityManagerService.java

├─ PermissionManagerService.checkPermission(...)
│ 或 PermissionManagerService.checkUidPermission(...)
│ frameworks/base/services/core/java/.../PermissionManagerService.java

├─ 检查步骤:
│ 1. 检查 uid 是否为 SYSTEM_UID / ROOT_UID(直接通过)
│ 2. 检查 uid 对应的 package 是否声明了该 permission
│ 3. 检查 permission 的 protectionLevel
│ 4. 对于 dangerous 权限,检查 AppOpsManager 状态
│ 5. 返回 PERMISSION_GRANTED 或 PERMISSION_DENIED

└─ 可选:AppOpsManager 的额外检查
frameworks/base/core/java/android/app/AppOpsManager.java
更细粒度的权限使用跟踪和限制

四.4 权限提升漏洞案例分析

案例 1:自定义权限 protectionLevel 配置错误

<!-- 应用 A 自定义了一个权限,但 protectionLevel 设为 normal -->
<permission
android:name="com.example.MY_SECRET_ACCESS"
android:protectionLevel="normal" />

<!-- 问题:任何应用安装时都能自动获取该权限 -->
<!-- 修复:改为 signature -->

案例 2:ContentProvider 权限设置不完整

<!-- 只设置了 readPermission 但没设置 writePermission -->
<provider
android:name=".MyProvider"
android:readPermission="com.example.READ"
android:authorities="com.example.provider"
android:exported="true" />
<!-- 没有设置 android:writePermission,导致任意应用可以写入数据 -->

<!-- 修复:同时设置 readPermission 和 writePermission -->

案例 3:Fragment Injection (Pre-Activity)

<!-- Activity 声明了 android:exported="true" 但没有权限保护 -->
<activity
android:name=".SettingsActivity"
android:exported="true" />
<!-- 攻击者可以通过 Intent extra 注入 PreferenceFragment -->

五、最小权限原则在实践中的应用

五.1 权限审计清单

逆向分析时,评估一个应用是否遵循最小权限原则的检查 checklist:

权限审计检查项:

[ ] 是否申请了与功能无关的权限?
- 一个简单的计算器应用是否申请了 CAMERA 和 CONTACTS?

[ ] 是否申请了多个同组的 dangerous 权限?
- 申请了 ACCESS_FINE_LOCATION,是否真的需要精确位置?
- 是否可以用 ACCESS_COARSE_LOCATION 代替?

[ ] 是否有未使用的权限声明?
- Manifest 中声明了但代码中从未调用

[ ] 自定义权限的 protectionLevel 是否正确?
- 内部通信权限是否设为 signature 而非 normal?

[ ] 导出组件是否都有权限保护?
- 所有 exported=true 的组件是否有 android:permission?

[ ] ContentProvider 的 readPermission 和 writePermission 是否都设置?
- 特别注意单边设置的情况

[ ] Intent Filter 是否过于宽泛?
- Action 是否过于通用导致被 Intent Spoofing?

五.2 使用代码扫描工具

# 使用 MobSF (Mobile Security Framework) 扫描权限
# https://github.com/MobSF/Mobile-Security-Framework-MobSF

# 使用 AndroBugs Framework
# https://github.com/AndroBugs/AndroBugs_Framework
python androbugs.py -f target.apk -o report/
# 会自动标记危险权限组合、导出组件缺失权限保护等问题

# 使用 QARK (Quick Android Review Kit)
# https://github.com/linkedin/qark
qark --apk target.apk --report-type html

五.3 AppOps 权限管理框架

Android 4.3 引入了 AppOps(Application Operations)框架,提供比 Permission 更细粒度的权限控制:

// AppOpsManager 的使用位置(Framework 内部)
// frameworks/base/services/core/java/com/android/server/AppOpsService.java

// 每个权限操作对应一个 AppOps code:
// OP_CAMERA → 26
// OP_READ_CONTACTS → 4
// OP_FINE_LOCATION → 1
// OP_READ_SMS → 14
// OP_RECORD_AUDIO → 27

// AppOps 模式:
// MODE_ALLOWED (0) → 允许
// MODE_IGNORED (1) → 拒绝但返回空数据(静默拒绝)
// MODE_ERRORED (2) → 拒绝并抛异常
// MODE_DEFAULT (3) → 使用默认策略

// 在逆向中可以 Hook AppOps 来修改权限检查结果
// frameworks/base/core/java/android/app/AppOpsManager.java
# 查看应用的 AppOps 设置
adb shell appops get com.target.app
# 示例输出:
# CAMERA: allow
# READ_CONTACTS: deny
# FINE_LOCATION: allow; time=+1h30m0s0ms ago

# 修改 AppOps 设置(需要 root / ADB shell)
adb shell appops set com.target.app CAMERA deny
adb shell appops set com.target.app READ_CONTACTS allow

六、AOSP 权限相关关键源码

模块 源码路径 关键内容
系统权限定义 /frameworks/base/core/res/AndroidManifest.xml 所有系统权限的标签、保护级别
ContextImpl /frameworks/base/core/java/android/app/ContextImpl.java checkPermission 入口
ActivityManagerService frameworks/base/services/core/java/.../am/ActivityManagerService.java checkComponentPermission
PermissionManagerService frameworks/base/services/core/java/.../pm/permission/PermissionManagerService.java 权限检查核心逻辑
AppOpsManager /frameworks/base/core/java/android/app/AppOpsManager.java AppOps 检查框架
AppOpsService frameworks/base/services/core/java/.../AppOpsService.java AppOps 服务端实现
PackageParser frameworks/base/core/java/android/content/pm/PackageParser.java APK 安装时解析权限
SELinux 策略 /system/sepolicy/ Android SELinux 策略文件
Process /frameworks/base/core/java/android/os/Process.java UID/SYSTEM_UID 定义
AID 映射 /system/core/libcutils/fs_config.cpp 用户/组 ID 映射

面试常考问题

Q1: Normal、Dangerous、Signature 权限的区别?Framework 层如何区分处理?

A:

Normal 权限:安装时静默授予,不弹出确认框(如 INTERNET、BLUETOOTH、ACCESS_NETWORK_STATE)。Framework 层在 PackageManagerService 安装应用时检查 protectionLevel,若为 normal 则直接记录为已授予,无需用户交互。

Dangerous 权限:涉及隐私数据,Android 6.0+ 需运行时通过 requestPermissions() 弹窗获取用户授权(如 CAMERA、CONTACTS、LOCATION)。Framework 层维护了一个权限授权状态表(runtime permission grant),每次 checkPermission 调用时需要同时检查 Manifest 声明 + 动态授权状态 + AppOps 状态。若 Manifest 声明了但未动态授权,返回 PERMISSION_DENIED。

Signature 权限:仅在请求应用的签名与声明该权限的系统应用签名相同时授予。Framework 层在应用安装时,PackageManagerService 计算请求者 APK 的签名证书指纹,与声明该权限的包的签名比较。如果签名不匹配,即使写入 Manifest 也不会被授予。

此外还有 Signature|Privileged 级别:需要同签名且位于 /system/priv-app/,用于更敏感的系统级权限(如 INSTALL_PACKAGES)。

AOSP 中对应的处理逻辑分布在:

  • /frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java(grantPermissions 方法)
  • /frameworks/base/core/java/android/content/pm/PackageParser.java(解析 Manifest 中的 uses-permission 声明)

Q2: 权限检查在 Framework 和 Kernel 层分别如何实现?两者之间的关系是什么?

A:

Framework 层:
PackageManager.checkPermission()ActivityManagerService.checkPermission() 调用链中完成。检查逻辑包括:(1)系统 UID/Root UID 直接放行;(2)调用 PermissionManagerService.checkUidPermission() 遍历 uid 下所有包,逐一检查是否有该权限的授权记录;(3)对于 dangerous 权限,额外检查 AppOpsManager 状态(用户可能通过设置页面临时关闭权限);(4)对于 isolated process 直接拒绝。源码位置:/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java

Kernel 层:
通过两层机制实现:(1)Linux DAC:基于 UID/GID 的文件系统和进程访问控制。每个应用运行在唯一的 UID 下,声明的权限映射为附加的 GID(如 INTERNET 权限 → inet GID 3003)。进程能否访问网络 socket 取决于其是否在 inet 组中。(2)SELinux MAC:基于安全策略的强制访问控制,即使 DAC 允许的操作也可能被 SELinux 拒绝。SELinux 策略在 /system/sepolicy/ 中定义,allow untrusted_app app_data_file:file { read write } 控制第三方应用的数据文件访问。

两者的关系:

  • Framework 层是”守门员”,决定用户空间中哪些调用能被放行。
  • Kernel 层是”地基”,Framework 的权限最终通过 UID/GID 和 SELinux 策略映射为底层能力。
  • Framework 层的检查可以被 Java 层面的 Hook(如 Xposed)绕过,但 Kernel 层的限制更难绕过(需要修改内核或 SELinux 策略)。
  • 两者配合形成纵深防御:即使 Framework 层被攻破,Kernel 层 SELinux 仍然可以阻止越权操作(如从普通 App 读取系统级文件)。

Q3: 如何检测一个应用是否存在权限提升漏洞?

A:

检测步骤和方法:

(1)检查导出组件权限保护:

  • 使用 aapt dump xmltree app.apk AndroidManifest.xml | grep -A 3 "activity\|service\|receiver\|provider" 列出所有组件,检查 exportedpermission 属性。
  • 重点检查:exported=true 但没有 permission 的组件;ContentProvider 的 readPermission/writePermission 是否成对设置。

(2)检查自定义权限的 protectionLevel:

  • 在 Manifest 中搜索 <permission> 标签,检查 protectionLevel 是否合理。
  • 如果 protectionLevel 为 normal(甚至未指定,默认为 normal),但用于保护敏感组件 → 存在风险。
  • 如果 protectionLevel 为 signature 但权限名称暗示内部使用,检查是否真的只有同签名应用能获取。

(3)检查 Intent Filter 的精确性:

  • 过于宽泛的 action(如自定义 action 但未做发送者校验)→ 可能被 Intent Spoofing。
  • BroadcastReceiver 接收到 Intent 后是否有权限校验(很多 BroadcastReceiver 不做验证就处理数据)。

(4)检查 debuggable 标志:

  • aapt dump badging app.apk | grep debuggable → 如果为 true,攻击者可以通过 adb run-as 访问应用数据。

(5)检查 backup 标志:

  • aapt dump badging app.apk | grep backup → allowBackup=true 允许通过 adb backup 导出应用数据。

(6)自动化工具辅助:

  • MobSF、QARK、drozer、AndroBugs Framework 等工具可以自动化检测上述大多数问题。

(7)常见漏洞模式:

  • 恶意应用调用无权限保护的导出 Activity 进行意图劫持
  • 自定义权限 protectionLevel 为 normal 导致任何应用都能获取敏感权限
  • ContentProvider 只设置了 readPermission 但 writePermission 留空
  • BroadcastReceiver 无条件处理外部 Intent 未验证调用者身份
  • 自定义权限定义了两个不同的包都声明了(权限保护失效)

Q4: SELinux 在 Android 权限体系中扮演什么角色?为什么即使绕过 Framework 层的权限检查,SELinux 还能保护系统?

A:

SELinux 是 Linux 内核的强制访问控制(MAC)模块,在 Android 5.0+ 默认以 Enforcing 模式运行。它独立于 Framework 层的权限检查,提供最底层的安全约束。

工作机制:
(1)每个进程和系统资源(文件、socket、设备等)都被分配了 SELinux 安全上下文(Security Context),格式为 user:role:type:mls
(2)内核中的 SELinux LSM(Linux Security Module)在每个系统调用时检查策略规则,决定是否放行。
(3)策略规则定义在 /system/sepolicy/.te 文件中,格式为 allow <source_type> <target_type>:<object_class> <permissions>

为什么能”兜底”:
(1)Framework 权限检查和 SELinux 策略检查是相互独立的两层。Framework 层的 checkPermission 返回 PERMISSION_GRANTED 只是 Java 层的判断,实际执行操作时内核仍然执行 SELinux 检查。

(2)示例:假设通过 Xposed 绕过了 Framework 层的权限检查,应用试图读取 /data/system/packages.xml。虽然 Framework 层被绕过,但 SELinux 策略中规定 allow untrusted_app system_data_file:file read 不被允许,内核仍然会拒绝该操作。

(3)SELinux 的 bypass 难度远高于 Framework 层:需要修改内核、修改 sepolicy 文件(需要 root)、或利用内核漏洞将 SELinux 降为 Permissive 模式。

(4)关键限制:SELinux 只能保护其定义了策略的资源。如果某个操作在策略中被允许(如 allow untrusted_app app_data_file:file read write 允许应用读写自己的数据),SELinux 就不会阻止。

AOSP 源码位置:

  • /system/sepolicy/ 下的 untrusted_app.te(第三方应用域策略)
  • /kernel/common/security/selinux/ 中的 LSM 实现
  • /external/selinux/ 中的用户空间工具

Q5: Android 权限从安装时到运行时的演进(API 23)在技术实现上做了哪些改动?为什么这个改动对逆向分析有影响?

A:

技术实现改动:

(1)安装时变化(PackageManagerService):

  • API 22 及之前:PMS 在安装时解析 Manifest 中所有 uses-permission,对于 dangerous 权限直接标记为 granted。
  • API 23 及之后:PMS 在安装时解析权限声明,但对于 dangerous 权限,初始状态标记为 “仅声明,未授权”(granted=false)。只有 normal 和 signature 权限在安装时自动授予。

(2)权限授权状态存储:

  • 新增了 Runtime Permission State 数据结构,存储在 /data/system/users/<user_id>/runtime-permissions.xml 中。
  • 每个应用的每个 dangerous 权限独立跟踪:是否已提示用户、用户选择、是否被 AppOps 覆盖。

(3)新增运行时交互 API:

  • Activity.requestPermissions(String[] permissions, int requestCode) → 发起权限请求弹窗
  • Context.checkSelfPermission(String permission) → 检查自身是否有权限
  • Activity.shouldShowRequestPermissionRationale(String permission) → 判断是否需要显示权限解释
  • Activity.onRequestPermissionsResult(int, String[], int[]) → 接收用户选择结果

(4)AppOps 集成:

  • 权限检查不再只依赖 PackageManager,还集成 AppOpsManager。
  • AppOps 记录每个权限操作的历史(允许/拒绝次数、上次时间),用户可以随时在设置中修改。

对逆向分析的影响:
(1)静态分析不足:单看 Manifest 中的权限声明无法判断应用是否真的获得该权限,必须运行时验证。
(2)Hook 点增多:除了传统的 checkPermission,还需要 Hook requestPermissions、onRequestPermissionsResult、AppOpsManager.checkOp 才能完整控制权限状态。
(3)权限请求混淆:应用可以在不同时机请求不同权限,难以通过一次静态扫描理解完整的权限使用模式。
(4)测试复杂性:每个权限组合(授予/拒绝/询问中)对应不同的应用行为路径,逆向分析时需要遍历所有情况。
(5)绕过手段增多:由于权限状态现在存储在用户空间 XML 文件中(而非内核),可以通过修改 /data/system/users/<id>/runtime-permissions.xml 或直接 Hook AppOpsManager 的 checkOp 返回值来模拟任意权限拥有状态。

打赏
  • 微信
  • 支付宝

评论