2. 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 判断是否可被劫持或任务栈注入。
解析脚本实现
使用 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 是否包含该标签。
exported 缺失且存在 intent-filter,安装本身就会失败,因此实际遇到的“隐式 exported”多出现在低版本 targetSdk 的 APK 中。审计时应同时记录 targetSdkVersion,作为风险上下文。