AndroidManifest保护:清单文件混淆、权限保护、组件保护

AndroidManifest.xml 这个文件,说白了就是应用的「身份证」加「通行证」。

我见过太多开发者,把精力全花在代码混淆上,却忽略了清单文件。结果呢?反编译一看,四大组件、权限声明、暴露的 Activity 一览无余。嗯,这就像你把家门锁得死死的,却在门口贴了张纸条:「钥匙在花盆底下」。

核心观点:清单文件是攻击者的第一份情报。保护它,等于切断了对方 30% 的信息来源。

1. 清单文件泄露了什么?

我们先看看,一个没保护的 AndroidManifest.xml 会暴露哪些信息:

  • 组件列表:所有 Activity、Service、Receiver、Provider 的完整类名
  • 权限声明:应用申请了哪些敏感权限
  • 导出状态:哪些组件是 exported="true" 的
  • Intent Filter:组件能接收哪些隐式意图
  • 元数据:API Key、渠道号、配置信息

我曾在一次渗透测试中,仅凭清单文件就找到了三个可被外部调用的导出组件。其中一个 Provider 居然没有做权限校验,直接可以读取数据库。你想想看,这有多危险?

2. 清单文件混淆:不只是改个名字

很多人以为清单文件混淆就是把包名改掉。其实远不止这些。

我个人习惯的做法是:

  1. 组件类名混淆:利用 ProGuard 的 -keep 规则,把清单中引用的类名也一并混淆。但要注意,清单中声明的类名必须和混淆后的保持一致。
  2. 字符串加密:把清单中的敏感字符串(如 API Key、自定义权限名)在构建时加密,运行时再解密。
  3. 动态注册组件:能不写在清单里的组件,尽量用代码动态注册。这样反编译后看不到组件列表。
// ProGuard 配置示例:保留清单中引用的类,但允许重命名
-keep,allowobfuscation class com.example.app.MainActivity
-keep,allowobfuscation class com.example.app.SecondActivity
// 注意:清单中声明的类必须显式 keep,否则混淆后找不到

小技巧:我习惯在构建脚本中写一个 Gradle Task,自动解析 AndroidManifest.xml,提取所有组件类名,然后生成对应的 ProGuard keep 规则。这样既不会漏掉,也不会多 keep 不该 keep 的类。

3. 权限保护:别让你的权限被滥用

权限保护这块,我踩过不少坑。

先看一个常见问题:自定义权限的 protectionLevel 设置不当。

protectionLevel 含义 风险等级
normal 安装时自动授予,无需用户确认
dangerous 运行时需用户确认
signature 仅同签名应用可获取 高(推荐)
signatureOrSystem 同签名或系统应用可获取

我曾经接手过一个项目,自定义权限用的是 normal 级别。结果呢?任何第三方应用只要声明了这个权限,就能调用我们暴露的接口。这等于把大门钥匙复制了无数份发出去。

避坑指南:自定义权限一定要用 signature 级别。除非你有特殊需求,否则别碰 normaldangerous。我曾经因为偷懒用了 normal,上线后被人利用,紧急发版修复,那叫一个狼狈。

另外,对于系统权限,我建议做「最小权限原则」:

  • 只申请真正需要的权限
  • 能用 uses-permission-sdk-23 的,别用 uses-permission
  • 敏感权限(如定位、相机)尽量在运行时申请,别写在清单里

4. 组件保护:把门锁好

组件保护是清单文件保护的重头戏。说白了,就是控制谁能调用你的组件。

4.1 导出控制

这是最基本也最容易出问题的地方。我见过太多 exported="true" 的组件,其实根本不需要对外暴露。

<!-- 错误示范:不必要地导出 -->
<activity android:name=".InternalActivity" android:exported="true" />

<!-- 正确做法:不导出,仅内部使用 -->
<activity android:name=".InternalActivity" android:exported="false" />

<!-- 如果有 Intent Filter,默认就是导出的,要小心 -->
<activity android:name=".ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
// 注意:有 intent-filter 的组件,exported 默认为 true

4.2 权限校验

对于必须导出的组件,一定要加权限校验。我常用的做法是:

  1. 在清单中声明自定义权限
  2. 在组件上添加 android:permission 属性
  3. 在代码中二次校验调用方的身份
<!-- 声明自定义权限 -->
<permission android:name="com.example.app.ACCESS_INTERNAL"
    android:protectionLevel="signature" />

<!-- 在组件上应用权限 -->
<provider android:name=".InternalProvider"
    android:exported="true"
    android:permission="com.example.app.ACCESS_INTERNAL" />

重要:清单中的权限声明只是第一道防线。我建议在代码中再做一次 checkCallingPermission() 校验。为什么?因为清单权限可以被绕过(比如通过反射调用)。双重校验才保险。

4.3 Intent 过滤保护

对于有 Intent Filter 的组件,攻击者可以通过构造隐式 Intent 来调用。我建议:

  • 尽量使用显式 Intent 调用内部组件
  • 对于必须使用隐式 Intent 的场景,在代码中校验 Intent 的来源和内容
  • 使用 setPackage() 限制 Intent 只能发给特定包名
// 安全地发送隐式 Intent
Intent intent = new Intent("com.example.action.CUSTOM");
intent.setPackage("com.example.app"); // 限制目标包名
startActivity(intent);

5. 知识体系总览

下面这张图,是我对 AndroidManifest 保护的整体理解。你可以把它当作一个检查清单:

AndroidManifest 保护体系 清单文件混淆 组件类名混淆(ProGuard) 敏感字符串加密 动态注册替代静态声明 权限保护 protectionLevel 设置 最小权限原则 运行时权限动态申请 组件保护 导出控制(exported) 权限校验(双重检查) Intent 过滤保护 核心原则:纵深防御,层层设卡 清单混淆 → 权限控制 → 组件保护 → 代码二次校验

6. 实战建议

最后,我总结几条实战中验证过的建议:

  • 清单文件要纳入版本管理:每次修改都要 review,别偷偷加权限
  • 用工具扫描:我常用 androguardqark 自动分析清单文件的安全问题
  • 测试导出组件:写自动化测试,验证每个导出组件是否做了权限校验
  • 别信默认值:Android 不同版本的默认 exported 值不一样,显式声明最安全

再次提醒:我曾经因为相信「默认就是安全的」,把一个 Provider 的 exported 留空。结果在 Android 12 上,默认值变成了 true。那一次线上事故,让我养成了「所有组件都显式声明 exported」的习惯。

清单文件保护,说白了就是「别让攻击者轻易拿到你的家底」。做好这三件事——混淆、权限、组件保护——你的应用就已经比市面上 80% 的应用安全了。


公众号:蓝海资料掘金营,微信deep3321