【Android逆向工程】第5章:Android 应用安全机制初探

88 阅读23分钟

第5章:Android 应用安全机制初探

目录


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>

安全风险:

  1. 隐式 Intent 劫持

    • 恶意应用可以注册相同的 intent-filter
    • 用户可能误启动恶意应用
  2. 数据泄露

    • 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:

  1. Started Service(启动式服务)

    • 通过 startService() 启动
    • 在后台运行,不返回结果
  2. 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 安全机制

注册方式

两种注册方式:

  1. 静态注册(AndroidManifest.xml)
<receiver
    android:name=".MyReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
  1. 动态注册(代码中)
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 设备可以访问

安全建议:

  1. 加密敏感数据
  2. 使用 SQLCipher 加密整个数据库
  3. 定期清理敏感数据

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+)

安全风险:

  • ⚠️ 其他应用可能读取文件
  • ⚠️ 用户可以直接访问文件
  • ⚠️ 卸载应用时文件可能残留

安全建议:

  1. 敏感数据不要存储在外部存储
  2. 如果必须存储,使用加密
  3. 使用应用专用目录(Android/data/)

5.4 日志输出安全风险

5.4.1 Logcat 日志系统

日志级别

Android 日志系统有 5 个级别:

级别方法说明使用场景
VERBOSELog.v()详细信息开发调试
DEBUGLog.d()调试信息开发调试
INFOLog.i()一般信息重要信息
WARNLog.w()警告信息潜在问题
ERRORLog.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

操作步骤:

  1. 打开 Android Studio
  2. 底部打开 "Logcat" 标签
  3. 选择设备和应用包名
  4. 使用搜索框过滤日志

界面说明:

[图示: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

使用图形化工具:

操作步骤:

  1. 提取数据库文件到 PC
  2. 使用工具打开 .db 文件
  3. 浏览表结构和数据
  4. 执行 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 中搜索:

  1. 搜索 "SharedPreferences"
  2. 搜索 "SQLiteDatabase"
  3. 搜索 "getSharedPreferences"
  4. 搜索 "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:总结应用的安全风险点

发现的安全风险:

  1. SharedPreferences 明文存储

    • ⚠️ Token 明文存储在 XML 文件中
    • ⚠️ 用户 ID 明文存储
    • 风险等级:
  2. SQLite 数据库未加密

    • ⚠️ 用户信息明文存储在数据库中
    • ⚠️ 数据库文件可以被提取
    • 风险等级:
  3. 日志泄露敏感信息

    • ⚠️ Token 在日志中明文输出
    • ⚠️ 用户 ID 在日志中输出
    • 风险等级:
  4. 组件导出配置

    • ⚠️ MainActivity 的 exported 为 true
    • ⚠️ 可能被外部应用调用
    • 风险等级:(主 Activity 通常需要导出)

安全建议:

  1. 加密存储敏感数据

    // 使用加密的 SharedPreferences
    String encryptedToken = encrypt(token);
    prefs.edit().putString("token", encryptedToken).commit();
    
  2. 使用 SQLCipher 加密数据库

    // 使用加密的数据库
    SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
        databasePath, password, null);
    
  3. 移除敏感日志

    // 不要记录敏感信息
    // Log.d(TAG, "User token: " + token);  // 删除这行
    
  4. 限制组件导出

    <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

解决方案:

  1. 使用 run-as(仅调试版本):
adb shell
run-as com.example.app
cd shared_prefs/
cat user_prefs.xml
  1. 使用 root 设备:

    • 刷入 root 权限
    • 使用 Magisk 等工具
  2. 分析反编译代码:

    • 使用 jadx 查看代码逻辑
    • 理解数据存储方式
    • 模拟数据访问
问题 2:SQLite 数据库被加密

症状:

sqlite3 user.db
# 提示:file is encrypted or is not a database

解决方案:

  1. 查找加密密钥:

    • 在代码中搜索加密相关函数
    • 使用 Frida Hook 获取密钥
    • 分析密钥生成逻辑
  2. 使用 SQLCipher 工具:

# 如果使用 SQLCipher,需要密码
sqlcipher user.db
PRAGMA key = 'password';
.tables
  1. 动态分析:
    • 使用 Frida Hook 数据库操作
    • 在运行时获取解密后的数据

5.9.2 日志查看问题

问题:Logcat 日志被过滤

症状:

adb logcat
# 没有输出或输出很少

解决方案:

  1. 调整日志级别:
# 查看所有级别
adb logcat *:V

# 查看特定级别
adb logcat *:D *:I *:W *:E
  1. 清除日志缓冲区:
adb logcat -c
# 然后重新运行应用
  1. 使用应用包名过滤:
adb logcat | grep com.example.app
  1. 检查应用日志配置:
    • 某些应用可能禁用了日志输出
    • 检查代码中是否有日志开关

5.9.3 权限问题

问题:无法访问应用数据目录

症状:

ls: cannot open directory: Permission denied

原因分析:

  • 非 root 设备权限限制
  • SELinux 策略限制
  • 应用数据目录权限设置

解决方案:

  1. 获取 root 权限:
adb shell
su
  1. 检查 SELinux 状态:
getenforce
# 如果是 Enforcing,可能需要临时设置为 Permissive
setenforce 0
  1. 修改目录权限(不推荐,可能破坏系统安全):
chmod 755 /data/data/com.example.app/

5.10 本章总结

5.10.1 知识点回顾

  1. Android 四大组件安全机制

    • Activity 的 exported 属性
    • Service 的权限控制
    • BroadcastReceiver 的注册安全
    • ContentProvider 的数据访问权限
  2. Intent 传递安全

    • 显式 Intent vs 隐式 Intent
    • Intent 数据泄露风险
    • Intent Filter 安全配置
  3. 数据存储安全

    • SharedPreferences 存储位置和权限
    • SQLite 数据库安全
    • 文件存储安全
  4. 日志安全

    • 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 应用的安全机制和常见风险点。在下一章中,我们将学习动态调试技术,通过运行时分析来发现更多安全问题。