第十二章:Android安全机制全景
本章字数:约20000字 阅读时间:约65分钟 难度等级:★★★★★
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。
引言
在前面的章节中,我们以攻击者的视角,成功突破了APP的安全防护。现在,让我们换一个角度,从防御者的视角来全面了解Android平台的安全机制。
理解这些安全机制,不仅能帮助我们更好地进行安全研究,也能指导我们设计更安全的应用。
12.1 Android安全架构概览
12.1.1 分层安全模型
┌─────────────────────────────────────────────────────────────────┐
│ Android安全架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 应用层安全 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ • 应用签名验证 │ │
│ │ • 权限系统 │ │
│ │ • 应用沙箱 │ │
│ │ • 代码混淆 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 框架层安全 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ • Binder IPC安全 │ │
│ │ • 系统服务权限检查 │ │
│ │ • Intent过滤 │ │
│ │ • ContentProvider权限 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Native层安全 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ • SELinux强制访问控制 │ │
│ │ • Seccomp系统调用过滤 │ │
│ │ • ASLR地址随机化 │ │
│ │ • Stack Canary栈保护 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 内核层安全 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ • Linux内核安全模块 │ │
│ │ • 进程隔离 │ │
│ │ • 文件系统权限 │ │
│ │ • 加密文件系统 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 硬件层安全 │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ • TrustZone可信执行环境 │ │
│ │ • 硬件密钥存储 │ │
│ │ • 安全启动链 │ │
│ │ • 硬件随机数生成器 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.1.2 安全设计原则
Android安全架构遵循以下核心原则:
1. 最小权限原则
- 应用默认没有任何权限
- 需要明确声明和用户授权
- 权限按需申请
2. 纵深防御原则
- 多层安全机制
- 单点突破不会导致全面失守
- 每层都有独立的安全检查
3. 隔离原则
- 应用之间相互隔离
- 用户数据与系统数据隔离
- 敏感操作在可信环境执行
12.2 应用签名机制
12.2.1 签名方案演进
┌─────────────────────────────────────────────────────────────────┐
│ APK签名方案演进 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 版本 引入时间 特点 │
│ ───────────────────────────────────────────────────────────── │
│ v1 (JAR) Android 1.0 基于JAR签名 │
│ 只签名ZIP条目 │
│ 可被篡改(添加文件) │
│ │
│ v2 Android 7.0 签名整个APK │
│ 防止任何修改 │
│ 安装验证更快 │
│ │
│ v3 Android 9.0 支持密钥轮换 │
│ 向后兼容 │
│ 更灵活的证书管理 │
│ │
│ v4 Android 11 增量安装支持 │
│ 流式验证 │
│ 更快的安装速度 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2.2 签名验证流程
/**
* APK签名验证示意(简化版)
*/
public class ApkSignatureVerifier {
/**
* 验证APK签名
*/
public boolean verifyApkSignature(File apkFile) {
try {
// 1. 尝试v4签名验证(Android 11+)
if (hasV4Signature(apkFile)) {
return verifyV4Signature(apkFile);
}
// 2. 尝试v3签名验证(Android 9+)
if (hasV3Signature(apkFile)) {
return verifyV3Signature(apkFile);
}
// 3. 尝试v2签名验证(Android 7+)
if (hasV2Signature(apkFile)) {
return verifyV2Signature(apkFile);
}
// 4. 回退到v1签名验证
return verifyV1Signature(apkFile);
} catch (Exception e) {
return false;
}
}
/**
* v2签名验证
* 签名块位于ZIP Central Directory之前
*/
private boolean verifyV2Signature(File apkFile) {
// 1. 定位APK签名块
// APK签名块位于ZIP Central Directory之前
// 格式: [Contents of ZIP entries] [APK Signing Block] [Central Directory] [EOCD]
// 2. 解析签名块
// 签名块包含多个ID-Value对
// ID 0x7109871a = v2签名
// 3. 验证每个签名者
// - 验证签名算法
// - 验证摘要
// - 验证证书链
// 4. 验证APK内容完整性
// 将APK分成1MB的块,计算每块的摘要
// 验证摘要树
return true;
}
}
12.2.3 签名的安全意义
1. 身份验证
- 确认APK来源
- 防止冒充官方应用
- 建立开发者信任
2. 完整性保护
- 检测APK是否被篡改
- 防止恶意代码注入
- 保护用户安全
3. 权限继承
- 相同签名的应用可共享权限
- 支持应用升级
- 实现应用间信任
12.3 权限系统
12.3.1 权限分类
┌─────────────────────────────────────────────────────────────────┐
│ 权限分类 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 保护级别 说明 示例 │
│ ───────────────────────────────────────────────────────────── │
│ normal 低风险权限 INTERNET │
│ 安装时自动授予 VIBRATE │
│ 无需用户确认 SET_WALLPAPER │
│ │
│ dangerous 高风险权限 READ_CONTACTS │
│ 需要用户明确授权 CAMERA │
│ 运行时请求 LOCATION │
│ │
│ signature 签名权限 BIND_DEVICE_ADMIN │
│ 仅相同签名应用可获得 MANAGE_DOCUMENTS │
│ 系统应用专用 │
│ │
│ signatureOrSystem 签名或系统权限 ACCESS_ALL_DOWNLOADS │
│ (已废弃) INSTALL_PACKAGES │
│ │
│ privileged 特权权限 DELETE_PACKAGES │
│ 仅系统特权应用 REBOOT │
│ │
└─────────────────────────────────────────────────────────────────┘
12.3.2 运行时权限
/**
* 运行时权限请求示例
*/
public class PermissionHelper {
private static final int REQUEST_CODE_PERMISSIONS = 100;
/**
* 检查并请求权限
*/
public void checkAndRequestPermissions(Activity activity, String[] permissions) {
List<String> permissionsToRequest = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(activity, permission)
!= PackageManager.PERMISSION_GRANTED) {
permissionsToRequest.add(permission);
}
}
if (!permissionsToRequest.isEmpty()) {
// 检查是否需要显示权限说明
for (String permission : permissionsToRequest) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// 显示权限说明对话框
showPermissionRationale(activity, permission);
return;
}
}
// 请求权限
ActivityCompat.requestPermissions(
activity,
permissionsToRequest.toArray(new String[0]),
REQUEST_CODE_PERMISSIONS
);
}
}
/**
* 处理权限请求结果
*/
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予
onPermissionGranted(permissions[i]);
} else {
// 权限被拒绝
onPermissionDenied(permissions[i]);
}
}
}
}
}
12.3.3 权限组
┌─────────────────────────────────────────────────────────────────┐
│ 危险权限组 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 权限组 包含的权限 │
│ ───────────────────────────────────────────────────────────── │
│ CALENDAR READ_CALENDAR, WRITE_CALENDAR │
│ │
│ CAMERA CAMERA │
│ │
│ CONTACTS READ_CONTACTS, WRITE_CONTACTS, │
│ GET_ACCOUNTS │
│ │
│ LOCATION ACCESS_FINE_LOCATION, │
│ ACCESS_COARSE_LOCATION, │
│ ACCESS_BACKGROUND_LOCATION │
│ │
│ MICROPHONE RECORD_AUDIO │
│ │
│ PHONE READ_PHONE_STATE, CALL_PHONE, │
│ READ_CALL_LOG, WRITE_CALL_LOG, │
│ ADD_VOICEMAIL, USE_SIP, │
│ PROCESS_OUTGOING_CALLS │
│ │
│ SENSORS BODY_SENSORS │
│ │
│ SMS SEND_SMS, RECEIVE_SMS, READ_SMS, │
│ RECEIVE_WAP_PUSH, RECEIVE_MMS │
│ │
│ STORAGE READ_EXTERNAL_STORAGE, │
│ WRITE_EXTERNAL_STORAGE │
│ │
└─────────────────────────────────────────────────────────────────┘
12.4 应用沙箱
12.4.1 沙箱机制原理
┌─────────────────────────────────────────────────────────────────┐
│ 应用沙箱架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ App A │ │ App B │ │ App C │ │
│ │ UID: 10001 │ │ UID: 10002 │ │ UID: 10003 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ /data/data/ │ │ /data/data/ │ │ /data/data/ │ │
│ │ com.app.a/ │ │ com.app.b/ │ │ com.app.c/ │ │
│ │ (700权限) │ │ (700权限) │ │ (700权限) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ════════════════════════════════════════════════════════ │
│ SELinux策略 │
│ ════════════════════════════════════════════════════════ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Linux内核 │ │
│ │ 进程隔离 / 文件系统权限 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
每个应用:
• 拥有唯一的Linux UID
• 运行在独立的进程中
• 拥有私有的数据目录
• 受SELinux策略约束
12.4.2 UID/GID分配
┌─────────────────────────────────────────────────────────────────┐
│ UID分配规则 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ UID范围 用途 │
│ ───────────────────────────────────────────────────────────── │
│ 0 root用户 │
│ 1000 system用户 │
│ 1001-1999 系统预定义用户 │
│ (radio, bluetooth, wifi等) │
│ 2000 shell用户 │
│ 10000-19999 普通应用 │
│ 50000-59999 隔离进程 │
│ 99000-99999 共享UID应用 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.4.3 数据隔离
/**
* 应用数据目录结构
*/
public class AppDataDirectories {
// 内部存储(私有)
// /data/data/<package_name>/
// 或 /data/user/<user_id>/<package_name>/
/**
* 私有文件目录
* 权限: 700 (仅应用自身可访问)
*/
public File getFilesDir(Context context) {
return context.getFilesDir();
// /data/data/<package>/files/
}
/**
* 私有缓存目录
*/
public File getCacheDir(Context context) {
return context.getCacheDir();
// /data/data/<package>/cache/
}
/**
* SharedPreferences目录
*/
public File getSharedPrefsDir(Context context) {
return new File(context.getDataDir(), "shared_prefs");
// /data/data/<package>/shared_prefs/
}
/**
* 数据库目录
*/
public File getDatabasePath(Context context, String name) {
return context.getDatabasePath(name);
// /data/data/<package>/databases/<name>
}
/**
* Native库目录
*/
public String getNativeLibraryDir(Context context) {
return context.getApplicationInfo().nativeLibraryDir;
// /data/app/<package>/lib/<abi>/
}
// 外部存储(Android 10+作用域存储)
/**
* 应用专属外部存储
*/
public File getExternalFilesDir(Context context, String type) {
return context.getExternalFilesDir(type);
// /storage/emulated/0/Android/data/<package>/files/<type>/
}
}
12.5 SELinux强制访问控制
12.5.1 SELinux基础
┌─────────────────────────────────────────────────────────────────┐
│ SELinux工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 传统Linux权限 (DAC - 自主访问控制): │
│ ───────────────────────────────────────────────────────────── │
│ • 基于用户/组/其他的rwx权限 │
│ • root用户可以绑过所有检查 │
│ • 一旦获得root,系统完全暴露 │
│ │
│ SELinux (MAC - 强制访问控制): │
│ ───────────────────────────────────────────────────────────── │
│ • 基于安全上下文的策略 │
│ • 即使root也受策略约束 │
│ • 最小权限原则 │
│ │
│ 安全上下文格式: │
│ user:role:type:level │
│ 例如: u:r:untrusted_app:s0:c512,c768 │
│ │
│ 策略规则示例: │
│ allow untrusted_app app_data_file:file { read write }; │
│ (允许untrusted_app类型读写app_data_file类型的文件) │
│ │
└─────────────────────────────────────────────────────────────────┘
12.5.2 Android SELinux域
┌─────────────────────────────────────────────────────────────────┐
│ 主要SELinux域 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 域 说明 │
│ ───────────────────────────────────────────────────────────── │
│ init init进程 │
│ kernel 内核线程 │
│ system_server 系统服务进程 │
│ zygote Zygote进程 │
│ untrusted_app 普通第三方应用 │
│ platform_app 平台签名应用 │
│ system_app 系统应用 │
│ priv_app 特权应用 │
│ isolated_app 隔离进程应用 │
│ shell adb shell │
│ su root进程 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.5.3 SELinux对逆向的影响
SELinux限制了以下操作:
1. 进程注入
• 无法ptrace其他应用进程
• 无法修改其他应用内存
2. 文件访问
• 无法读取其他应用私有数据
• 无法修改系统文件
3. 网络操作
• 某些端口受限
• 某些协议受限
4. 设备访问
• 无法直接访问硬件设备
• 需要通过系统服务
绕过方法(需要root):
• setenforce 0 (切换到permissive模式)
• 修改SELinux策略
• 使用Magisk等工具
12.6 代码保护技术
12.6.1 代码混淆
┌─────────────────────────────────────────────────────────────────┐
│ 代码混淆技术 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 混淆类型 效果 工具 │
│ ───────────────────────────────────────────────────────────── │
│ 名称混淆 类/方法/字段名 ProGuard │
│ 变为无意义字符 R8 │
│ │
│ 控制流混淆 插入虚假分支 DexGuard │
│ 打乱执行顺序 Allatori │
│ │
│ 字符串加密 运行时解密字符串 DexGuard │
│ 防止静态分析 自定义实现 │
│ │
│ 反射调用 隐藏真实调用 自定义实现 │
│ 增加分析难度 │
│ │
│ Native化 关键代码转为Native DexProtector │
│ 提高逆向难度 自定义实现 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.6.2 ProGuard/R8配置示例
# proguard-rules.pro
# 保留应用入口
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
# 保留JNI方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留序列化类
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留枚举
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 混淆字典(使用难以阅读的字符)
-obfuscationdictionary dictionary.txt
-classobfuscationdictionary dictionary.txt
-packageobfuscationdictionary dictionary.txt
# 优化选项
-optimizationpasses 5
-allowaccessmodification
-repackageclasses ''
12.6.3 字符串加密实现
/**
* 字符串加密工具
*/
public class StringEncryptor {
private static final byte[] KEY = {0x12, 0x34, 0x56, 0x78,
0x9A, 0xBC, 0xDE, 0xF0};
/**
* 加密字符串(编译时使用)
*/
public static String encrypt(String plainText) {
byte[] data = plainText.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = new byte[data.length];
for (int i = 0; i < data.length; i++) {
encrypted[i] = (byte) (data[i] ^ KEY[i % KEY.length]);
}
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* 解密字符串(运行时使用)
*/
public static String decrypt(String encryptedText) {
byte[] encrypted = Base64.getDecoder().decode(encryptedText);
byte[] decrypted = new byte[encrypted.length];
for (int i = 0; i < encrypted.length; i++) {
decrypted[i] = (byte) (encrypted[i] ^ KEY[i % KEY.length]);
}
return new String(decrypted, StandardCharsets.UTF_8);
}
}
// 使用示例
// 原始代码:
// String apiUrl = "https://api.example.com/v1/data";
// 加密后:
// String apiUrl = StringEncryptor.decrypt("Wm9uZXMgYXBpLmV4YW1wbGUuY29tL3YxL2RhdGE=");
12.7 运行时保护
12.7.1 反调试技术
/**
* 反调试检测
*/
public class AntiDebug {
/**
* 检测是否被调试
*/
public static boolean isBeingDebugged() {
// 方法1: 检查调试标志
if (android.os.Debug.isDebuggerConnected()) {
return true;
}
// 方法2: 检查TracerPid
if (getTracerPid() != 0) {
return true;
}
// 方法3: 检查调试端口
if (isDebugPortOpen()) {
return true;
}
return false;
}
/**
* 获取TracerPid
*/
private static int getTracerPid() {
try {
BufferedReader reader = new BufferedReader(
new FileReader("/proc/self/status"));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("TracerPid:")) {
return Integer.parseInt(line.substring(10).trim());
}
}
reader.close();
} catch (Exception e) {
// ignore
}
return 0;
}
/**
* 检查调试端口
*/
private static boolean isDebugPortOpen() {
try {
// 检查常见调试端口
int[] debugPorts = {23946, 27042, 27043}; // Frida, IDA等
for (int port : debugPorts) {
try (java.net.Socket socket = new java.net.Socket()) {
socket.connect(new java.net.InetSocketAddress("127.0.0.1", port), 100);
return true;
} catch (Exception e) {
// 端口未开放
}
}
} catch (Exception e) {
// ignore
}
return false;
}
/**
* Native层反调试
*/
public static native boolean nativeAntiDebug();
}
12.7.2 Native层反调试
// anti_debug.c
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <pthread.h>
// 检测ptrace
int detect_ptrace() {
// 尝试ptrace自己,如果已被调试则会失败
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
return 1;
}
// 解除ptrace
ptrace(PTRACE_DETACH, 0, NULL, NULL);
return 0;
}
// 检测/proc/self/status中的TracerPid
int detect_tracer_pid() {
FILE *fp = fopen("/proc/self/status", "r");
if (fp == NULL) return 0;
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "TracerPid:", 10) == 0) {
int pid = atoi(line + 10);
fclose(fp);
return pid != 0;
}
}
fclose(fp);
return 0;
}
// 检测Frida
int detect_frida() {
// 检测Frida特征文件
const char *frida_files[] = {
"/data/local/tmp/frida-server",
"/data/local/tmp/re.frida.server",
NULL
};
for (int i = 0; frida_files[i] != NULL; i++) {
if (access(frida_files[i], F_OK) == 0) {
return 1;
}
}
// 检测Frida端口
// ...
return 0;
}
// 反调试线程
void *anti_debug_thread(void *arg) {
while (1) {
if (detect_ptrace() || detect_tracer_pid() || detect_frida()) {
// 检测到调试,退出进程
exit(0);
}
sleep(1);
}
return NULL;
}
// 启动反调试
JNIEXPORT jboolean JNICALL
Java_com_example_AntiDebug_nativeAntiDebug(JNIEnv *env, jclass clazz) {
// 启动反调试线程
pthread_t thread;
pthread_create(&thread, NULL, anti_debug_thread, NULL);
// 立即检测一次
return detect_ptrace() || detect_tracer_pid() || detect_frida();
}
12.7.3 完整性校验
/**
* 应用完整性校验
*/
public class IntegrityChecker {
private final Context context;
public IntegrityChecker(Context context) {
this.context = context;
}
/**
* 检查APK签名
*/
public boolean verifySignature() {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
// 计算签名哈希
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(signature.toByteArray());
String signatureHash = bytesToHex(hash);
// 与预期签名比较
if (!signatureHash.equals(EXPECTED_SIGNATURE_HASH)) {
return false;
}
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* 检查安装来源
*/
public boolean verifyInstaller() {
String installer = context.getPackageManager()
.getInstallerPackageName(context.getPackageName());
// 检查是否从官方商店安装
return "com.android.vending".equals(installer) || // Google Play
"com.huawei.appmarket".equals(installer) || // 华为应用市场
"com.xiaomi.market".equals(installer); // 小米应用商店
}
/**
* 检查是否被重打包
*/
public boolean checkRepackaging() {
try {
// 检查classes.dex的哈希
String apkPath = context.getPackageCodePath();
String dexHash = calculateDexHash(apkPath);
return dexHash.equals(EXPECTED_DEX_HASH);
} catch (Exception e) {
return false;
}
}
/**
* 检查运行环境
*/
public boolean checkEnvironment() {
// 检查是否在模拟器中运行
if (isEmulator()) {
return false;
}
// 检查是否root
if (isRooted()) {
return false;
}
// 检查是否有Xposed框架
if (hasXposed()) {
return false;
}
return true;
}
/**
* 检测模拟器
*/
private boolean isEmulator() {
return Build.FINGERPRINT.contains("generic") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.HARDWARE.contains("goldfish") ||
Build.HARDWARE.contains("ranchu");
}
/**
* 检测root
*/
private boolean isRooted() {
// 检查su文件
String[] paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/data/local/su"
};
for (String path : paths) {
if (new File(path).exists()) {
return true;
}
}
// 检查Magisk
if (new File("/sbin/.magisk").exists()) {
return true;
}
return false;
}
/**
* 检测Xposed
*/
private boolean hasXposed() {
try {
// 检查Xposed类
Class.forName("de.robv.android.xposed.XposedBridge");
return true;
} catch (ClassNotFoundException e) {
// 未找到Xposed
}
// 检查堆栈中是否有Xposed
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
if (element.getClassName().contains("xposed")) {
return true;
}
}
return false;
}
private static final String EXPECTED_SIGNATURE_HASH = "...";
private static final String EXPECTED_DEX_HASH = "...";
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
private String calculateDexHash(String apkPath) throws Exception {
// 实现DEX哈希计算
return "";
}
}
12.8 网络安全
12.8.1 证书固定
/**
* 证书固定实现
*/
public class CertificatePinning {
/**
* 创建带证书固定的OkHttpClient
*/
public static OkHttpClient createPinnedClient() {
// 证书固定配置
CertificatePinner certificatePinner = new CertificatePinner.Builder()
// 固定主域名证书
.add("api.dreamworld.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
// 固定备用证书
.add("api.dreamworld.com",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
// 固定根证书
.add("*.dreamworld.com",
"sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=")
.build();
return new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
}
/**
* 使用Network Security Config(推荐)
*/
// res/xml/network_security_config.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.dreamworld.com</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
</pin-set>
</domain-config>
</network-security-config>
*/
}
12.8.2 代理检测
/**
* 代理检测
*/
public class ProxyDetector {
/**
* 检测系统代理
*/
public static boolean hasSystemProxy() {
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
return proxyHost != null && !proxyHost.isEmpty();
}
/**
* 检测WiFi代理
*/
public static boolean hasWifiProxy(Context context) {
try {
WifiManager wifiManager = (WifiManager) context
.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
// 检查代理设置...
} catch (Exception e) {
// ignore
}
return false;
}
/**
* 检测VPN
*/
public static boolean hasVpn(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network activeNetwork = cm.getActiveNetwork();
if (activeNetwork != null) {
NetworkCapabilities caps = cm.getNetworkCapabilities(activeNetwork);
if (caps != null) {
return caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
}
}
}
return false;
}
/**
* 绕过代理直连
*/
public static OkHttpClient createNoProxyClient() {
return new OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.build();
}
}
12.9 本章小结
本章我们从防御者的视角,全面分析了Android平台的安全机制:
12.9.1 技术要点回顾
┌─────────────────────────────────────────────────────────────────┐
│ 本章技术要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 安全架构 │
│ • 分层安全模型 │
│ • 纵深防御原则 │
│ • 最小权限原则 │
│ │
│ 2. 应用签名 │
│ • v1/v2/v3/v4签名方案 │
│ • 签名验证流程 │
│ • 签名的安全意义 │
│ │
│ 3. 权限系统 │
│ • 权限分类与保护级别 │
│ • 运行时权限 │
│ • 权限组机制 │
│ │
│ 4. 应用沙箱 │
│ • UID/GID隔离 │
│ • 数据目录隔离 │
│ • 进程隔离 │
│ │
│ 5. SELinux │
│ • 强制访问控制 │
│ • 安全上下文 │
│ • 策略规则 │
│ │
│ 6. 代码保护 │
│ • 代码混淆 │
│ • 字符串加密 │
│ • Native化 │
│ │
│ 7. 运行时保护 │
│ • 反调试技术 │
│ • 完整性校验 │
│ • 环境检测 │
│ │
│ 8. 网络安全 │
│ • 证书固定 │
│ • 代理检测 │
│ • 传输加密 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.9.2 攻防对照表
┌─────────────────────────────────────────────────────────────────┐
│ 攻防对照 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 防护措施 攻击方法 难度 │
│ ───────────────────────────────────────────────────────────── │
│ 代码混淆 反混淆工具 ★★☆☆☆ │
│ 字符串加密 动态分析/Hook ★★★☆☆ │
│ 证书固定 Frida绕过 ★★★☆☆ │
│ 反调试 反反调试 ★★★★☆ │
│ 完整性校验 重打包绕过 ★★★★☆ │
│ Native保护 Unidbg模拟 ★★★★★ │
│ SELinux 需要root ★★★★★ │
│ 硬件安全 几乎无法绕过 ★★★★★ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.9.3 安全建议
对于开发者:
- 使用多层防护,不依赖单一机制
- 关键逻辑放在Native层
- 使用证书固定防止中间人攻击
- 实现运行时完整性校验
- 定期更新安全策略
对于安全研究员:
- 了解各层安全机制的原理
- 掌握绕过技术的边界
- 遵守法律法规和道德准则
- 负责任地披露漏洞
12.9.4 下一章预告
在下一章《Native层安全深度剖析》中,我们将深入探讨:
- ELF文件格式
- SO加载机制
- Native层保护技术
- 反汇编与逆向分析
- Unidbg高级技巧
本章完