2. AndroidManifest 组件暴露面审计

AndroidManifest 组件暴露面审计 学习卡片

审计目标与判定模型

AndroidManifest.xml 是 APK 的“对外契约”,组件暴露面审计的核心是回答三个问题:哪些组件可被外部应用启动、通过什么 Intent 触发、触发后是否受权限保护。在 Android 12(API 31)及以上,只要组件声明了 intent-filter,android:exported 就必须显式声明,否则安装失败;因此解析脚本必须同时处理“显式 exported”与“隐式 exported(有 filter 且未声明)”两种情况。

判定优先级为:exported="false" 直接排除;exported="true" 或“有 intent-filter 且未声明 exported”进入候选;再检查 android:permission 是否为空或为普通权限,最后结合 taskAffinity、launchMode 判断是否可被劫持或任务栈注入。

AndroidManifest 组件暴露面判定链 AndroidManifest.xml application / activity service / receiver / provider ① exported 判定 exported="true" → 候选 有 intent-filter 且未声明 → 候选 exported="false" → 排除 ② 权限保护判定 android:permission 为空 或为 normal/dangerous → 无有效保护 ③ 风险 taskAffinity launchMode grantUriPermission 组件类型 × 暴露条件 × 风险等级 组件类型 暴露条件 典型风险 风险等级 Activity exported=true + 无权限 界面劫持 / 越权操作 高 Service exported=true + 无权限 后台任务注入 / 数据泄露 中高 Receiver exported=true + 无权限 广播伪造 / 逻辑绕过 中高 Provider exported=true + 无权限 SQL 注入 / 文件遍历 高 输出:高风险暴露组件清单 组件名 | 类型 | exported | intent-filter | permission | taskAffinity | 风险等级

解析脚本实现

使用 Python 的 xml.etree.ElementTree 解析反编译后的 AndroidManifest.xml。脚本需要处理命名空间、组件类型遍历、intent-filter 提取和权限判定。以下代码可直接运行,输入为 apktool 解包后的 AndroidManifest.xml 路径。

import xml.etree.ElementTree as ET
import sys

NS = {'android': 'http://schemas.android.com/apk/res/android'}
COMPONENT_TAGS = ['activity', 'activity-alias', 'service', 'receiver', 'provider']

def parse_manifest(path):
    tree = ET.parse(path)
    root = tree.getroot()
    pkg = root.get('package', '')
    results = []

    for tag in COMPONENT_TAGS:
        for comp in root.iter(tag):
            name = comp.get(f'{{{NS["android"]}}}name', '')
            exported = comp.get(f'{{{NS["android"]}}}exported')
            permission = comp.get(f'{{{NS["android"]}}}permission', '')
            task_affinity = comp.get(f'{{{NS["android"]}}}taskAffinity', '')
            launch_mode = comp.get(f'{{{NS["android"]}}}launchMode', '')

            filters = []
            for intent_filter in comp.findall('intent-filter'):
                actions = [a.get(f'{{{NS["android"]}}}name', '')
                           for a in intent_filter.findall('action')]
                categories = [c.get(f'{{{NS["android"]}}}name', '')
                              for c in intent_filter.findall('category')]
                filters.append({'actions': actions, 'categories': categories})

            has_filter = len(filters) > 0
            if exported == 'false':
                continue
            if exported == 'true' or (exported is None and has_filter):
                risk = assess_risk(tag, permission, task_affinity, launch_mode)
                results.append({
                    'name': name, 'type': tag, 'exported': exported or 'implicit',
                    'permission': permission or '(none)',
                    'taskAffinity': task_affinity or '(default)',
                    'launchMode': launch_mode or 'standard',
                    'filters': filters, 'risk': risk
                })
    return pkg, results

def assess_risk(tag, permission, task_affinity, launch_mode):
    if permission:
        return '低'
    if tag in ('provider', 'activity'):
        return '高'
    if tag in ('service', 'receiver'):
        return '中高'
    return '中'

if __name__ == '__main__':
    pkg, findings = parse_manifest(sys.argv[1])
    print(f'Package: {pkg}')
    print(f'Exposed components: {len(findings)}')
    for f in findings:
        print(f"[{f['risk']}] {f['type']} {f['name']} "
              f"exported={f['exported']} perm={f['permission']} "
              f"affinity={f['taskAffinity']} mode={f['launchMode']}")

关键判定逻辑说明

  • 隐式 exported:当 exported 属性缺失且存在 intent-filter 时,Android 默认该组件可被外部访问。脚本用 exported is None and has_filter 捕获此情况。
  • 权限保护:android:permission 为空表示无保护;若声明了 normal 或 dangerous 级别权限,攻击者仍可通过申请同权限绕过,因此脚本将“有权限声明”统一降为低风险,实际审计中需进一步核对权限保护级别。
  • taskAffinity:非默认值(非包名)且 launchMode 为 singleTask 或 singleInstance 时,存在任务栈劫持风险,脚本在输出中保留该字段供人工复核。

运行与验证

以例 1 中 apktool 解包得到的目录为例,执行:

python audit_manifest.py ./decoded/AndroidManifest.xml

预期输出格式如下(示例数据,非真实 APK 结果):

Package: com.example.target
Exposed components: 3
[高] activity com.example.target.MainActivity exported=true perm=(none) affinity=(default) mode=standard
[中高] receiver com.example.target.PushReceiver exported=implicit perm=(none) affinity=(default) mode=standard
[低] service com.example.target.SyncService exported=true perm=com.example.target.permission.SYNC affinity=(default) mode=standard

验证要点:将脚本输出与 aapt2 dump xmltree --file AndroidManifest.xml target.apk 的结果逐项比对,确认组件数量、exported 值和 intent-filter 动作一致。若脚本遗漏了 activity-alias,需检查 COMPONENT_TAGS 是否包含该标签。

对于 Android 12+ 的 APK,若 exported 缺失且存在 intent-filter,安装本身就会失败,因此实际遇到的“隐式 exported”多出现在低版本 targetSdk 的 APK 中。审计时应同时记录 targetSdkVersion,作为风险上下文。