第5章:Android 应用安全机制初探
目录
- 5.1 Android 四大组件安全机制
- 5.2 Intent 传递与数据泄露风险
- 5.3 数据存储安全
- 5.4 日志输出安全风险
- 5.5 工具使用:安全机制分析
- 5.6 代码对照:配置与安全风险
- 5.7 实战案例:提取敏感数据
- 5.8 安全建议与最佳实践
- 5.9 常见问题与解决方案
5.1 Android 四大组件安全机制
5.1.1 Activity 安全机制
exported 属性
exported 属性说明:
exported 属性决定 Activity 是否可以被其他应用启动。
配置方式:
<!-- AndroidManifest.xml -->
<activity
android:name=".MainActivity"
android:exported="true" /> <!-- 可以被其他应用启动 -->
安全影响:
| exported 值 | 说明 | 安全风险 |
|---|---|---|
true | 可以被其他应用启动 | ⚠️ 高风险:可能被恶意应用调用 |
false | 只能被本应用启动 | ✅ 安全:防止外部调用 |
| 未设置 | 根据 intent-filter 自动判断 | ⚠️ 需检查 intent-filter |
自动判断规则:
- 如果 Activity 有
intent-filter,且未设置exported,则默认为true - 如果 Activity 没有
intent-filter,且未设置exported,则默认为false
安全示例:
<!-- 不安全的配置 -->
<activity
android:name=".LoginActivity"
android:exported="true" /> <!-- 危险!登录页面可以被外部调用 -->
<!-- 安全的配置 -->
<activity
android:name=".LoginActivity"
android:exported="false" /> <!-- 安全:只能内部启动 -->
intent-filter 安全
intent-filter 说明:
intent-filter 定义了 Activity 可以响应的 Intent 类型。
配置示例:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
安全风险:
-
隐式 Intent 劫持
- 恶意应用可以注册相同的 intent-filter
- 用户可能误启动恶意应用
-
数据泄露
- Intent 中传递的敏感数据可能被其他应用获取
安全建议:
<!-- 使用显式 Intent(推荐) -->
<!-- 代码中: -->
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
<!-- 避免使用隐式 Intent -->
<!-- 不推荐: -->
Intent intent = new Intent("com.example.ACTION");
startActivity(intent);
5.1.2 Service 安全机制
Service 类型
两种 Service:
-
Started Service(启动式服务)
- 通过
startService()启动 - 在后台运行,不返回结果
- 通过
-
Bound Service(绑定式服务)
- 通过
bindService()绑定 - 提供客户端-服务器接口
- 通过
Service 权限控制
配置示例:
<service
android:name=".MyService"
android:exported="true"
android:permission="com.example.PERMISSION" />
安全属性:
| 属性 | 说明 | 安全影响 |
|---|---|---|
exported | 是否可被外部应用访问 | true 时需配合权限使用 |
permission | 启动/绑定服务所需的权限 | 限制访问者 |
enabled | 服务是否启用 | 可以临时禁用服务 |
安全配置:
<!-- 不安全的配置 -->
<service
android:name=".PaymentService"
android:exported="true" /> <!-- 危险!支付服务可被外部调用 -->
<!-- 安全的配置 -->
<service
android:name=".PaymentService"
android:exported="false" /> <!-- 安全:只能内部使用 -->
<!-- 或使用权限保护 -->
<service
android:name=".PaymentService"
android:exported="true"
android:permission="com.example.PAYMENT_PERMISSION" />
5.1.3 BroadcastReceiver 安全机制
注册方式
两种注册方式:
- 静态注册(AndroidManifest.xml)
<receiver
android:name=".MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
- 动态注册(代码中)
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.ACTION");
registerReceiver(receiver, filter);
安全风险
风险 1:敏感广播泄露
<!-- 不安全的配置 -->
<receiver
android:name=".SMSReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<!-- 危险!短信接收器可被外部应用注册 -->
风险 2:广播劫持
- 恶意应用可以注册相同的 intent-filter
- 可能拦截或修改广播内容
安全配置:
<!-- 安全的配置 -->
<receiver
android:name=".MyReceiver"
android:exported="false"> <!-- 只能接收本应用发送的广播 -->
<intent-filter>
<action android:name="com.example.INTERNAL_ACTION" />
</intent-filter>
</receiver>
<!-- 或使用权限保护 -->
<receiver
android:name=".MyReceiver"
android:exported="true"
android:permission="com.example.BROADCAST_PERMISSION">
<intent-filter>
<action android:name="com.example.ACTION" />
</intent-filter>
</receiver>
5.1.4 ContentProvider 安全机制
数据访问权限
配置示例:
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_PERMISSION"
android:writePermission="com.example.WRITE_PERMISSION" />
权限属性:
| 属性 | 说明 | 安全影响 |
|---|---|---|
exported | 是否可被外部应用访问 | true 时需配合权限使用 |
readPermission | 读取数据所需的权限 | 限制读取操作 |
writePermission | 写入数据所需的权限 | 限制写入操作 |
permission | 读写通用权限 | 同时限制读写 |
安全配置:
<!-- 不安全的配置 -->
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="true" /> <!-- 危险!用户数据可被任意访问 -->
<!-- 安全的配置 -->
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="false" /> <!-- 安全:只能内部访问 -->
<!-- 或使用权限保护 -->
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_USER_DATA"
android:writePermission="com.example.WRITE_USER_DATA" />
5.2 Intent 传递与数据泄露风险
5.2.1 Intent 数据传递
Intent 类型
显式 Intent:
// 明确指定目标组件
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("username", "admin");
intent.putExtra("password", "admin123"); // 危险!密码在 Intent 中
startActivity(intent);
隐式 Intent:
// 通过 Action 启动,不指定具体组件
Intent intent = new Intent("com.example.ACTION");
intent.putExtra("data", sensitiveData); // 危险!可能被其他应用接收
startActivity(intent);
5.2.2 Intent 数据泄露风险
风险 1:Intent 中传递敏感数据
问题代码:
// 不安全的做法
Intent intent = new Intent(this, PaymentActivity.class);
intent.putExtra("credit_card", "1234-5678-9012-3456"); // 危险!
intent.putExtra("cvv", "123"); // 危险!
startActivity(intent);
风险分析:
- Intent 数据可能被其他应用读取
- 系统日志可能记录 Intent 内容
- 恶意应用可能注册相同的 intent-filter 拦截数据
安全做法:
// 安全的做法:使用安全存储
// 1. 将敏感数据存储到安全位置(加密的 SharedPreferences)
String token = getSecureToken();
// 2. 只传递标识符
Intent intent = new Intent(this, PaymentActivity.class);
intent.putExtra("payment_id", paymentId); // 只传递 ID
startActivity(intent);
// 3. 在目标 Activity 中从安全存储读取
String token = getSecureToken(paymentId);
风险 2:隐式 Intent 的安全问题
问题代码:
// 不安全的隐式 Intent
Intent intent = new Intent("com.example.SEND_DATA");
intent.putExtra("user_data", userData);
startActivity(intent); // 可能被恶意应用拦截
安全做法:
// 1. 使用显式 Intent(推荐)
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("data", data);
startActivity(intent);
// 2. 如果必须使用隐式 Intent,验证目标应用
Intent intent = new Intent("com.example.ACTION");
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
if (activities.size() == 1) {
// 只有一个应用可以处理,相对安全
startActivity(intent);
} else {
// 多个应用可以处理,存在风险
// 使用显式 Intent 或显示选择器
}
5.2.3 Intent Filter 安全
不安全的配置:
<!-- 过于宽泛的 intent-filter -->
<activity
android:name=".DataActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <!-- 危险!匹配所有类型 -->
</intent-filter>
</activity>
安全配置:
<!-- 具体的 intent-filter -->
<activity
android:name=".DataActivity"
android:exported="true">
<intent-filter>
<action android:name="com.example.VIEW_DATA" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" /> <!-- 具体类型 -->
</intent-filter>
</activity>
5.3 数据存储安全
5.3.1 SharedPreferences 存储安全
SharedPreferences 简介
SharedPreferences 是 Android 提供的轻量级键值对存储方式。
使用方式:
// 写入数据
SharedPreferences prefs = getSharedPreferences("user_prefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("username", "admin");
editor.putString("token", "abc123xyz"); // 危险!token 未加密
editor.commit();
// 读取数据
String token = prefs.getString("token", null);
存储位置
文件位置:
/data/data/<包名>/shared_prefs/<文件名>.xml
访问权限:
- 默认权限:
-rw-rw----(只有应用本身和同 UID 的应用可以访问) - 非 root 设备:其他应用无法直接访问
- root 设备:可以访问所有应用的数据
查看文件:
# 需要 root 权限
adb shell
su
cd /data/data/com.example.app/shared_prefs/
ls -la
cat user_prefs.xml
安全风险
风险 1:明文存储敏感数据
问题代码:
SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
prefs.edit()
.putString("password", "admin123") // 危险!明文存储
.putString("api_key", "sk-123456") // 危险!API 密钥明文
.commit();
存储文件内容:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="password">admin123</string>
<string name="api_key">sk-123456</string>
</map>
安全做法:
// 使用加密存储
String encryptedPassword = encrypt("admin123");
prefs.edit()
.putString("password", encryptedPassword) // 存储加密后的数据
.commit();
// 读取时解密
String encrypted = prefs.getString("password", null);
String password = decrypt(encrypted);
风险 2:MODE_WORLD_READABLE(已废弃)
不安全的模式:
// Android 7.0 之前可以使用,但非常危险
SharedPreferences prefs = getSharedPreferences("prefs", MODE_WORLD_READABLE);
// 任何应用都可以读取这个文件
注意: Android 7.0+ 已废弃此模式,使用会抛出异常。
5.3.2 SQLite 数据库存储安全
SQLite 简介
SQLite 是 Android 内置的关系型数据库。
使用方式:
// 创建数据库
SQLiteDatabase db = openOrCreateDatabase("user.db", MODE_PRIVATE, null);
// 创建表
db.execSQL("CREATE TABLE IF NOT EXISTS users (" +
"id INTEGER PRIMARY KEY, " +
"username TEXT, " +
"password TEXT)"); // 危险!密码未加密
// 插入数据
ContentValues values = new ContentValues();
values.put("username", "admin");
values.put("password", "admin123"); // 危险!
db.insert("users", null, values);
存储位置
数据库文件位置:
/data/data/<包名>/databases/<数据库名>.db
/data/data/<包名>/databases/<数据库名>.db-journal # 日志文件
查看数据库:
# 需要 root 权限
adb shell
su
cd /data/data/com.example.app/databases/
ls -la
sqlite3 user.db
安全风险
风险 1:明文存储敏感数据
问题代码:
// 不安全的存储
db.execSQL("INSERT INTO users (username, password) VALUES (?, ?)",
new String[]{"admin", "admin123"}); // 密码明文存储
数据库内容:
sqlite> SELECT * FROM users;
id|username|password
1|admin|admin123 -- 密码明文可见
安全做法:
// 1. 加密敏感字段
String encryptedPassword = encrypt("admin123");
db.execSQL("INSERT INTO users (username, password) VALUES (?, ?)",
new String[]{"admin", encryptedPassword});
// 2. 使用 SQLCipher 加密整个数据库
// 需要添加依赖:net.zetetic:android-database-sqlcipher
风险 2:数据库文件权限
默认权限:
- 数据库文件:
-rw-rw----(只有应用本身可以访问) - 非 root 设备相对安全
- root 设备可以访问
安全建议:
- 加密敏感数据
- 使用 SQLCipher 加密整个数据库
- 定期清理敏感数据
5.3.3 文件存储安全
内部存储
存储位置:
/data/data/<包名>/files/
/data/data/<包名>/cache/
访问权限:
- 默认只有应用本身可以访问
- 其他应用无法访问(非 root)
使用方式:
// 写入文件
FileOutputStream fos = openFileOutput("data.txt", MODE_PRIVATE);
fos.write("sensitive data".getBytes());
fos.close();
// 读取文件
FileInputStream fis = openFileInput("data.txt");
// ...
外部存储
存储位置:
/storage/emulated/0/Android/data/<包名>/files/
/sdcard/Android/data/<包名>/files/
访问权限:
- 所有应用都可以访问(如果知道路径)
- 需要存储权限(Android 10+)
安全风险:
- ⚠️ 其他应用可能读取文件
- ⚠️ 用户可以直接访问文件
- ⚠️ 卸载应用时文件可能残留
安全建议:
- 敏感数据不要存储在外部存储
- 如果必须存储,使用加密
- 使用应用专用目录(Android/data/)
5.4 日志输出安全风险
5.4.1 Logcat 日志系统
日志级别
Android 日志系统有 5 个级别:
| 级别 | 方法 | 说明 | 使用场景 |
|---|---|---|---|
| VERBOSE | Log.v() | 详细信息 | 开发调试 |
| DEBUG | Log.d() | 调试信息 | 开发调试 |
| INFO | Log.i() | 一般信息 | 重要信息 |
| WARN | Log.w() | 警告信息 | 潜在问题 |
| ERROR | Log.e() | 错误信息 | 错误信息 |
日志输出
代码示例:
Log.v("TAG", "Verbose message");
Log.d("TAG", "Debug message");
Log.i("TAG", "Info message");
Log.w("TAG", "Warning message");
Log.e("TAG", "Error message");
5.4.2 敏感信息泄露风险
风险 1:密码和 Token 泄露
问题代码:
// 不安全的日志输出
String password = editText.getText().toString();
Log.d("Login", "Password: " + password); // 危险!密码泄露
String token = api.getToken();
Log.i("API", "Token: " + token); // 危险!Token 泄露
Logcat 输出:
D/Login: Password: admin123
I/API: Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
安全做法:
// 1. 不记录敏感信息
String password = editText.getText().toString();
// Log.d("Login", "Password: " + password); // 不要记录
// 2. 使用占位符
Log.d("Login", "Password length: " + password.length());
// 3. 使用混淆或哈希
Log.d("Login", "Password hash: " + hashPassword(password));
风险 2:API 密钥泄露
问题代码:
String apiKey = "sk-1234567890abcdef";
Log.d("Config", "API Key: " + apiKey); // 危险!
安全做法:
// 1. 不记录 API 密钥
// Log.d("Config", "API Key: " + apiKey); // 不要记录
// 2. 只记录部分信息
Log.d("Config", "API Key: sk-****" + apiKey.substring(apiKey.length() - 4));
风险 3:用户数据泄露
问题代码:
User user = getUser();
Log.d("User", "User data: " + user.toString()); // 可能包含敏感信息
安全做法:
// 只记录非敏感信息
Log.d("User", "User ID: " + user.getId());
// 不记录:密码、邮箱、手机号等
5.4.3 日志查看方法
使用 adb logcat
基本命令:
# 查看所有日志
adb logcat
# 按标签过滤
adb logcat -s TAG
# 按级别过滤
adb logcat *:E # 只显示 ERROR 级别
# 按包名过滤
adb logcat | grep com.example.app
# 保存到文件
adb logcat > logcat.txt
# 清除日志
adb logcat -c
过滤敏感信息:
# 搜索密码相关日志
adb logcat | grep -i password
# 搜索 token 相关日志
adb logcat | grep -i token
# 搜索 API 相关日志
adb logcat | grep -i api
使用 Android Studio Logcat
操作步骤:
- 打开 Android Studio
- 底部打开 "Logcat" 标签
- 选择设备和应用包名
- 使用搜索框过滤日志
界面说明:
[图示:Android Studio Logcat 界面]
┌─────────────────────────────────────────┐
│ [设备选择] [应用选择] [日志级别] [搜索框] │
├─────────────────────────────────────────┤
│ 时间 级别 标签 进程ID 消息 │
│ 10:30:15 D Login 12345 Password:...│
│ 10:30:16 I API 12345 Token: ... │
└─────────────────────────────────────────┘
5.5 工具使用:安全机制分析
5.5.1 使用 aapt 查看应用权限
aapt dump badging
基本命令:
# 查看应用基本信息
aapt dump badging app.apk
# 只查看权限
aapt dump badging app.apk | grep permission
# 查看包名和版本
aapt dump badging app.apk | grep package
输出示例:
package: name='com.example.app' versionCode='1' versionName='1.0'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE'
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE'
aapt dump permissions
命令:
# 查看所有权限
aapt dump permissions app.apk
输出示例:
package: com.example.app
uses-permission: android.permission.INTERNET
uses-permission: android.permission.READ_EXTERNAL_STORAGE
uses-permission: android.permission.WRITE_EXTERNAL_STORAGE
uses-permission: android.permission.ACCESS_NETWORK_STATE
5.5.2 使用 adb shell 访问应用数据
查看应用数据目录
基本命令:
# 进入 shell
adb shell
# 切换到应用数据目录(需要 root)
su
cd /data/data/com.example.app/
# 查看目录结构
ls -la
# 查看文件权限
ls -la shared_prefs/
ls -la databases/
ls -la files/
目录结构:
/data/data/com.example.app/
├── shared_prefs/ # SharedPreferences 文件
│ └── user_prefs.xml
├── databases/ # SQLite 数据库
│ └── user.db
├── files/ # 内部文件
│ └── data.txt
└── cache/ # 缓存文件
提取文件
提取 SharedPreferences:
# 在设备上
cat /data/data/com.example.app/shared_prefs/user_prefs.xml
# 或使用 adb pull
adb pull /data/data/com.example.app/shared_prefs/user_prefs.xml ./
提取数据库:
# 在设备上查看
sqlite3 /data/data/com.example.app/databases/user.db
.tables
SELECT * FROM users;
# 或使用 adb pull
adb pull /data/data/com.example.app/databases/user.db ./
⚠️ 重要提示:
- 非 root 设备无法直接访问其他应用的数据目录
- 需要 root 权限或使用
run-as命令(仅限调试版本)
使用 run-as(仅调试版本):
# 切换到应用用户(仅调试版本)
adb shell
run-as com.example.app
cd shared_prefs/
cat user_prefs.xml
5.5.3 使用 adb logcat 查看日志
基本命令
# 查看所有日志
adb logcat
# 按标签过滤
adb logcat -s Login:*
# 按级别过滤
adb logcat *:E *:W # 只显示 ERROR 和 WARN
# 按包名过滤
adb logcat | grep com.example.app
# 实时监控并保存
adb logcat -v time > logcat.txt
过滤敏感信息
搜索密码:
adb logcat | grep -i password
搜索 Token:
adb logcat | grep -i token
搜索 API 密钥:
adb logcat | grep -i "api.*key\|apikey\|api_key"
5.5.4 使用 SQLite 工具查看数据库
在设备上使用 sqlite3
# 进入 shell
adb shell
su
# 打开数据库
sqlite3 /data/data/com.example.app/databases/user.db
# 查看表
.tables
# 查看表结构
.schema users
# 查询数据
SELECT * FROM users;
# 退出
.quit
在 PC 上使用 SQLite 工具
提取数据库:
# 提取数据库文件
adb pull /data/data/com.example.app/databases/user.db ./
# 使用 SQLite 工具打开
sqlite3 user.db
使用图形化工具:
- DB Browser for SQLite:sqlitebrowser.org/
- SQLiteStudio:sqlitestudio.pl/
操作步骤:
- 提取数据库文件到 PC
- 使用工具打开 .db 文件
- 浏览表结构和数据
- 执行 SQL 查询
5.5.5 使用文本编辑器查看 SharedPreferences
提取文件:
# 提取 XML 文件
adb pull /data/data/com.example.app/shared_prefs/user_prefs.xml ./
# 使用文本编辑器打开
cat user_prefs.xml
# 或
vim user_prefs.xml
XML 文件格式:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="username">admin</string>
<string name="token">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...</string>
<int name="user_id" value="12345" />
<boolean name="is_logged_in" value="true" />
</map>
5.6 代码对照:配置与安全风险
5.6.1 Activity 配置对照
不安全的配置
<!-- AndroidManifest.xml -->
<activity
android:name=".LoginActivity"
android:exported="true"> <!-- 危险!登录页面可被外部调用 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <!-- 危险!过于宽泛 -->
</intent-filter>
</activity>
安全风险:
- ⚠️ 恶意应用可以启动登录页面
- ⚠️ 可能绕过登录流程
- ⚠️ Intent 数据可能被拦截
安全的配置
<!-- AndroidManifest.xml -->
<activity
android:name=".LoginActivity"
android:exported="false"> <!-- 安全:只能内部启动 -->
</activity>
<!-- 或使用权限保护 -->
<activity
android:name=".LoginActivity"
android:exported="true"
android:permission="com.example.LOGIN_PERMISSION"> <!-- 需要权限 -->
</activity>
5.6.2 Service 配置对照
不安全的配置
<service
android:name=".PaymentService"
android:exported="true" /> <!-- 危险!支付服务可被外部调用 -->
安全风险:
- ⚠️ 恶意应用可以绑定服务
- ⚠️ 可能窃取支付信息
- ⚠️ 可能发起未授权支付
安全的配置
<service
android:name=".PaymentService"
android:exported="false" /> <!-- 安全:只能内部使用 -->
<!-- 或使用权限保护 -->
<service
android:name=".PaymentService"
android:exported="true"
android:permission="com.example.PAYMENT_PERMISSION" />
5.6.3 BroadcastReceiver 配置对照
不安全的配置
<receiver
android:name=".SMSReceiver"
android:exported="true"> <!-- 危险! -->
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
安全风险:
- ⚠️ 恶意应用可以注册相同的接收器
- ⚠️ 可能拦截短信
- ⚠️ 可能窃取验证码
安全的配置
<receiver
android:name=".SMSReceiver"
android:exported="false" <!-- 安全:只能接收内部广播 -->
android:permission="android.permission.BROADCAST_SMS"> <!-- 系统权限 -->
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
5.6.4 ContentProvider 配置对照
不安全的配置
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="true" /> <!-- 危险!用户数据可被任意访问 -->
安全风险:
- ⚠️ 任何应用都可以访问数据
- ⚠️ 可能泄露用户隐私
- ⚠️ 可能被恶意修改
安全的配置
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="false" /> <!-- 安全:只能内部访问 -->
<!-- 或使用权限保护 -->
<provider
android:name=".UserDataProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_USER_DATA"
android:writePermission="com.example.WRITE_USER_DATA" />
5.6.5 Permission 声明对照
权限声明
<!-- 声明自定义权限 -->
<permission
android:name="com.example.READ_USER_DATA"
android:protectionLevel="normal" /> <!-- 或 "dangerous", "signature" -->
保护级别:
| 级别 | 说明 | 用户是否需要授权 |
|---|---|---|
normal | 普通权限 | ❌ 自动授予 |
dangerous | 危险权限 | ✅ 需要用户授权 |
signature | 签名权限 | ❌ 自动授予(同签名应用) |
signatureOrSystem | 签名或系统权限 | ❌ 系统应用自动授予 |
权限使用
<!-- 使用权限 -->
<uses-permission android:name="com.example.READ_USER_DATA" />
5.7 实战案例:提取敏感数据
5.7.1 创建测试应用
目标: 分析一个存储用户信息的 App,提取敏感数据(token、用户 ID)。
测试应用代码:
package com.example.userapp;
import android.app.Activity;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
private static final String TAG = "UserApp";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 存储用户信息到 SharedPreferences
SharedPreferences prefs = getSharedPreferences("user_data", MODE_PRIVATE);
prefs.edit()
.putString("username", "admin")
.putString("token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
.putInt("user_id", 12345)
.commit();
// 记录日志(包含敏感信息)
Log.d(TAG, "User token: " + prefs.getString("token", null)); // 危险!
Log.d(TAG, "User ID: " + prefs.getInt("user_id", 0));
// 存储到数据库
SQLiteDatabase db = openOrCreateDatabase("user.db", MODE_PRIVATE, null);
db.execSQL("CREATE TABLE IF NOT EXISTS users (" +
"id INTEGER PRIMARY KEY, " +
"username TEXT, " +
"email TEXT)");
db.execSQL("INSERT INTO users (username, email) VALUES (?, ?)",
new String[]{"admin", "admin@example.com"});
db.close();
}
}
编译为 APK: user_app.apk
5.7.2 步骤 1:反编译 APK,查看 AndroidManifest.xml
反编译:
apktool d user_app.apk -o user_app_decompiled
查看 AndroidManifest.xml:
cat user_app_decompiled/AndroidManifest.xml
或使用 aapt:
aapt dump xmltree user_app.apk AndroidManifest.xml
分析内容:
<manifest package="com.example.userapp">
<application>
<activity
android:name=".MainActivity"
android:exported="true"> <!-- 注意:exported 属性 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
分析结果:
- 包名:
com.example.userapp - 主 Activity:
MainActivity - 数据存储位置:
/data/data/com.example.userapp/
5.7.3 步骤 2:分析应用权限和数据存储方式
查看权限:
aapt dump permissions user_app.apk
使用 jadx 分析代码:
jadx-gui user_app.apk
在 jadx 中搜索:
- 搜索 "SharedPreferences"
- 搜索 "SQLiteDatabase"
- 搜索 "getSharedPreferences"
- 搜索 "openOrCreateDatabase"
定位到的代码:
// jadx 反编译结果
SharedPreferences prefs = getSharedPreferences("user_data", 0);
prefs.edit()
.putString("username", "admin")
.putString("token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
.putInt("user_id", 12345)
.commit();
SQLiteDatabase db = openOrCreateDatabase("user.db", 0, null);
分析结果:
- SharedPreferences 文件名:
user_data.xml - 数据库文件名:
user.db - 存储位置:
/data/data/com.example.userapp/
5.7.4 步骤 3:使用 adb shell 访问应用数据目录
连接设备并获取 root:
adb shell
su
切换到应用数据目录:
cd /data/data/com.example.userapp/
ls -la
查看目录结构:
# 查看所有目录
ls -la
# 输出示例:
# drwxrwx--x u0_a123 u0_a123 shared_prefs/
# drwxrwx--x u0_a123 u0_a123 databases/
# drwxrwx--x u0_a123 u0_a123 files/
⚠️ 重要提示:
- 需要 root 权限才能访问其他应用的数据目录
- 非 root 设备可以使用
run-as(仅调试版本)
使用 run-as(调试版本):
# 不需要 root,但需要是调试版本
adb shell
run-as com.example.userapp
cd shared_prefs/
cat user_data.xml
5.7.5 步骤 4:提取 SharedPreferences 文件并解析
提取文件:
# 方法1:在设备上直接查看
adb shell
su
cat /data/data/com.example.userapp/shared_prefs/user_data.xml
# 方法2:提取到 PC
adb pull /data/data/com.example.userapp/shared_prefs/user_data.xml ./
cat user_data.xml
文件内容:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="username">admin</string>
<string name="token">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NSwidXNlcm5hbWUiOiJhZG1pbiJ9...</string>
<int name="user_id" value="12345" />
</map>
提取的敏感信息:
- ✅ 用户名:
admin - ✅ Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ✅ 用户 ID:
12345
使用 Python 解析:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
解析 SharedPreferences XML 文件
"""
import xml.etree.ElementTree as ET
def parse_shared_prefs(xml_file):
"""解析 SharedPreferences XML 文件"""
tree = ET.parse(xml_file)
root = tree.getroot()
print("=" * 60)
print("SharedPreferences 内容")
print("=" * 60)
for child in root:
name = child.get('name')
tag = child.tag
if tag == 'string':
value = child.text
print(f"{name}: {value}")
elif tag == 'int':
value = child.get('value')
print(f"{name}: {value}")
elif tag == 'boolean':
value = child.get('value')
print(f"{name}: {value}")
# ... 其他类型
if __name__ == "__main__":
parse_shared_prefs("user_data.xml")
5.7.6 步骤 5:提取 SQLite 数据库并查看内容
提取数据库:
# 提取数据库文件
adb pull /data/data/com.example.userapp/databases/user.db ./
查看数据库内容:
方法 1:使用 sqlite3 命令行
sqlite3 user.db
# 查看表
.tables
# 查看表结构
.schema users
# 查询数据
SELECT * FROM users;
# 退出
.quit
方法 2:使用 Python 脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
解析 SQLite 数据库
"""
import sqlite3
def parse_database(db_file):
"""解析 SQLite 数据库"""
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 获取所有表名
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cursor.fetchall()
print("=" * 60)
print("数据库表列表")
print("=" * 60)
for table in tables:
print(f" - {table[0]}")
# 查询每个表的数据
for table in tables:
table_name = table[0]
print(f"\n表: {table_name}")
print("-" * 60)
cursor.execute(f"SELECT * FROM {table_name};")
rows = cursor.fetchall()
# 获取列名
cursor.execute(f"PRAGMA table_info({table_name});")
columns = [row[1] for row in cursor.fetchall()]
print(f"列: {', '.join(columns)}")
print()
for row in rows:
print(row)
conn.close()
if __name__ == "__main__":
parse_database("user.db")
输出示例:
数据库表列表
- users
表: users
------------------------------------------------------------
列: id, username, email
(1, 'admin', 'admin@example.com')
提取的敏感信息:
- ✅ 用户名:
admin - ✅ 邮箱:
admin@example.com
5.7.7 步骤 6:分析 Logcat 日志中的敏感信息
启动应用并监控日志:
# 清除旧日志
adb logcat -c
# 启动应用
adb shell am start -n com.example.userapp/.MainActivity
# 实时查看日志
adb logcat | grep -i "userapp\|token\|user"
日志输出:
D/UserApp: User token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
D/UserApp: User ID: 12345
提取的敏感信息:
- ✅ Token:从日志中直接可见
- ✅ 用户 ID:从日志中直接可见
保存日志到文件:
# 保存所有日志
adb logcat -v time > logcat.txt
# 只保存应用相关日志
adb logcat -v time | grep com.example.userapp > app_logcat.txt
# 搜索敏感信息
grep -i "token\|password\|api" logcat.txt
5.7.8 步骤 7:总结应用的安全风险点
发现的安全风险:
-
SharedPreferences 明文存储
- ⚠️ Token 明文存储在 XML 文件中
- ⚠️ 用户 ID 明文存储
- 风险等级:高
-
SQLite 数据库未加密
- ⚠️ 用户信息明文存储在数据库中
- ⚠️ 数据库文件可以被提取
- 风险等级:高
-
日志泄露敏感信息
- ⚠️ Token 在日志中明文输出
- ⚠️ 用户 ID 在日志中输出
- 风险等级:中
-
组件导出配置
- ⚠️ MainActivity 的 exported 为 true
- ⚠️ 可能被外部应用调用
- 风险等级:低(主 Activity 通常需要导出)
安全建议:
-
加密存储敏感数据
// 使用加密的 SharedPreferences String encryptedToken = encrypt(token); prefs.edit().putString("token", encryptedToken).commit(); -
使用 SQLCipher 加密数据库
// 使用加密的数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( databasePath, password, null); -
移除敏感日志
// 不要记录敏感信息 // Log.d(TAG, "User token: " + token); // 删除这行 -
限制组件导出
<activity android:name=".MainActivity" android:exported="false" /> <!-- 如果不需要外部调用 -->
5.8 安全建议与最佳实践
5.8.1 组件安全配置
Activity 安全配置
最佳实践:
<!-- 1. 默认设置 exported="false" -->
<activity
android:name=".InternalActivity"
android:exported="false" />
<!-- 2. 需要外部调用时使用权限 -->
<activity
android:name=".PublicActivity"
android:exported="true"
android:permission="com.example.ACCESS_ACTIVITY" />
<!-- 3. 使用具体的 intent-filter -->
<activity
android:name=".DataActivity"
android:exported="true">
<intent-filter>
<action android:name="com.example.VIEW_DATA" />
<data android:mimeType="application/json" /> <!-- 具体类型 -->
</intent-filter>
</activity>
Service 安全配置
最佳实践:
<!-- 1. 内部服务不导出 -->
<service
android:name=".InternalService"
android:exported="false" />
<!-- 2. 外部服务使用权限 -->
<service
android:name=".PublicService"
android:exported="true"
android:permission="com.example.ACCESS_SERVICE" />
BroadcastReceiver 安全配置
最佳实践:
<!-- 1. 内部广播不导出 -->
<receiver
android:name=".InternalReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.example.INTERNAL_ACTION" />
</intent-filter>
</receiver>
<!-- 2. 系统广播使用系统权限 -->
<receiver
android:name=".SMSReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
5.8.2 数据存储安全
SharedPreferences 安全
最佳实践:
// 1. 敏感数据加密存储
String encryptedData = encrypt(sensitiveData);
prefs.edit().putString("key", encryptedData).commit();
// 2. 使用加密库(如 EncryptedSharedPreferences)
// AndroidX Security Crypto
EncryptedSharedPreferences.create(
"prefs",
MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build(),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
SQLite 数据库安全
最佳实践:
// 1. 使用 SQLCipher 加密数据库
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
databasePath,
password, // 加密密钥
null
);
// 2. 敏感字段单独加密
String encryptedEmail = encrypt(email);
db.execSQL("INSERT INTO users (email) VALUES (?)",
new String[]{encryptedEmail});
5.8.3 日志安全
最佳实践:
// 1. 生产环境禁用调试日志
if (BuildConfig.DEBUG) {
Log.d(TAG, "Debug message");
}
// 2. 不记录敏感信息
// ❌ Log.d(TAG, "Password: " + password);
// ✅ Log.d(TAG, "Password length: " + password.length());
// 3. 使用占位符
Log.d(TAG, "Token: ****" + token.substring(token.length() - 4));
// 4. 使用 ProGuard 移除日志
// 在 proguard-rules.pro 中:
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}
5.8.4 Intent 安全
最佳实践:
// 1. 优先使用显式 Intent
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("data", data);
startActivity(intent);
// 2. 避免在 Intent 中传递敏感数据
// ❌ intent.putExtra("password", password);
// ✅ 使用安全存储,只传递 ID
// 3. 验证 Intent 数据
String data = getIntent().getStringExtra("data");
if (data != null && isValid(data)) {
// 处理数据
}
5.9 常见问题与解决方案
5.9.1 数据访问问题
问题 1:非 root 设备无法访问其他应用数据
症状:
adb shell
cd /data/data/com.example.app/
# 提示:Permission denied
解决方案:
- 使用 run-as(仅调试版本):
adb shell
run-as com.example.app
cd shared_prefs/
cat user_prefs.xml
-
使用 root 设备:
- 刷入 root 权限
- 使用 Magisk 等工具
-
分析反编译代码:
- 使用 jadx 查看代码逻辑
- 理解数据存储方式
- 模拟数据访问
问题 2:SQLite 数据库被加密
症状:
sqlite3 user.db
# 提示:file is encrypted or is not a database
解决方案:
-
查找加密密钥:
- 在代码中搜索加密相关函数
- 使用 Frida Hook 获取密钥
- 分析密钥生成逻辑
-
使用 SQLCipher 工具:
# 如果使用 SQLCipher,需要密码
sqlcipher user.db
PRAGMA key = 'password';
.tables
- 动态分析:
- 使用 Frida Hook 数据库操作
- 在运行时获取解密后的数据
5.9.2 日志查看问题
问题:Logcat 日志被过滤
症状:
adb logcat
# 没有输出或输出很少
解决方案:
- 调整日志级别:
# 查看所有级别
adb logcat *:V
# 查看特定级别
adb logcat *:D *:I *:W *:E
- 清除日志缓冲区:
adb logcat -c
# 然后重新运行应用
- 使用应用包名过滤:
adb logcat | grep com.example.app
- 检查应用日志配置:
- 某些应用可能禁用了日志输出
- 检查代码中是否有日志开关
5.9.3 权限问题
问题:无法访问应用数据目录
症状:
ls: cannot open directory: Permission denied
原因分析:
- 非 root 设备权限限制
- SELinux 策略限制
- 应用数据目录权限设置
解决方案:
- 获取 root 权限:
adb shell
su
- 检查 SELinux 状态:
getenforce
# 如果是 Enforcing,可能需要临时设置为 Permissive
setenforce 0
- 修改目录权限(不推荐,可能破坏系统安全):
chmod 755 /data/data/com.example.app/
5.10 本章总结
5.10.1 知识点回顾
-
Android 四大组件安全机制
- Activity 的 exported 属性
- Service 的权限控制
- BroadcastReceiver 的注册安全
- ContentProvider 的数据访问权限
-
Intent 传递安全
- 显式 Intent vs 隐式 Intent
- Intent 数据泄露风险
- Intent Filter 安全配置
-
数据存储安全
- SharedPreferences 存储位置和权限
- SQLite 数据库安全
- 文件存储安全
-
日志安全
- Logcat 日志系统
- 敏感信息泄露风险
- 日志查看和分析方法
5.10.2 实践要点
- ✅ 理解 Android 安全机制的工作原理
- ✅ 能够分析应用的安全配置
- ✅ 能够提取和分析敏感数据
- ✅ 了解常见的安全风险点
5.10.3 下一步学习
- 第 6 章:学习动态调试技术(ADB + Logcat)
- 第 7 章:使用 Frida 进行动态 Hook
- 第 10 章:抓包工具实战
附录:安全检查清单
组件安全检查
- 所有 Activity 的 exported 属性是否正确设置
- 需要外部访问的组件是否使用权限保护
- Intent Filter 是否过于宽泛
- 是否使用显式 Intent(避免隐式 Intent)
数据存储安全检查
- 敏感数据是否加密存储
- SharedPreferences 是否存储敏感信息
- SQLite 数据库是否加密
- 文件存储位置是否安全
日志安全检查
- 是否在日志中输出敏感信息
- 生产环境是否禁用调试日志
- 是否使用 ProGuard 移除日志
权限检查
- 申请的权限是否必要
- 自定义权限的保护级别是否合适
- 权限使用是否正确
本章完成! 🎉
现在你已经了解了 Android 应用的安全机制和常见风险点。在下一章中,我们将学习动态调试技术,通过运行时分析来发现更多安全问题。