执行摘要
PrivacySentry 是一套完整的 Android 隐私合规检测解决方案,采用编译期注解 + 字节码插桩 + 运行时 Hook 的三层架构设计。总共包含 67 个 Kotlin/Java 文件,通过 ASM 和 Booster 框架在编译期对敏感 API 调用进行拦截和代理,运行时记录完整的隐私合规数据。
核心技术栈
- Gradle Plugin 基于 AGP 8.0+
- 字节码操作 ASM 9.1
- Transform 框架 Booster
- 语言 Kotlin 1.8.10 + Java 8
- 最低支持 Android API 19
第一部分:架构总体设计
1.1 四层架构模型
┌─────────────────────────────────────────────────────────────┐
│ 4. 代理层 (privacy-proxy) │
│ - 预置的 API 拦截实现 │
│ - 使用注解声明拦截规则 │
└─────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────┐
│ 3. Hook 层 (hook-sentry) │
│ - 运行时 SDK 初始化和管理 │
│ - 缓存机制(内存、磁盘) │
│ - 日志收集和文件输出 │
└─────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────┐
│ 2. 插件层 (plugin-sentry) │
│ - Gradle 编译期 Transform 执行 │
│ - ASM 字节码操作和修改 │
│ - Booster 框架集成 │
└─────────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────────┐
│ 1. 注解层 (privacy-annotation) │
│ - 编译期注解定义 │
│ - 运行时不可见 (@Retention(CLASS)) │
└─────────────────────────────────────────────────────────────┘
1.2 项目模块说明
| 模块 |
路径 |
职责 |
关键类 |
| privacy-annotation |
/privacy-annotation |
注解定义 |
@PrivacyMethodProxy, @PrivacyClassProxy |
| plugin-sentry |
/plugin-sentry |
Gradle 插件和字节码转换 |
PrivacySentryPlugin, Transform 类 |
| hook-sentry |
/hook-sentry |
运行时 SDK 和日志收集 |
PrivacySentry, 缓存管理器 |
| privacy-proxy |
/privacy-proxy |
预置的拦截实现 |
PrivacyProxyCall, 各种 Proxy 类 |
| privacy-replace |
/privacy-replace |
类替换功能(已废弃) |
- |
第二部分:注解层详细分析
2.1 核心注解系统
文件位置: privacy-annotation/src/main/java/com/yl/lib/privacy_annotation/
2.1.1 @PrivacyMethodProxy - 方法代理注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PrivacyMethodProxy {
Class originalClass(); // 原始类
String originalMethod() default ""; // 原始方法名
int originalOpcode() default MethodInvokeOpcode.INVOKESTATIC;
boolean ignoreClass() default false;
}
用途: 标记需要代理的方法。编译期插件通过该注解收集需要拦截的方法列表。
关键字段说明
originalClass: 要拦截的目标类
originalMethod: 要拦截的目标方法名
originalOpcode: 调用方式(静态/虚实例/接口等)
ignoreClass: 允许只按方法名和签名匹配
2.1.2 MethodInvokeOpcode 定义
| Opcode |
值 |
含义 |
使用场景 |
INVOKEVIRTUAL |
182 |
调用实例方法 |
普通实例方法 |
INVOKESPECIAL |
183 |
调用特殊方法 |
私有方法、构造函数 |
INVOKESTATIC |
184 |
调用静态方法 |
静态方法 |
INVOKEINTERFACE |
185 |
调用接口方法 |
接口实现 |
第三部分:插件层详细分析
3.1 Gradle 插件入口
文件: PrivacySentryPlugin.kt
class PrivacySentryPlugin : Plugin<Project> {
override fun apply(project: Project) {
// 1. 创建配置扩展点
var extension = project.extensions.create("privacy", PrivacyExtension::class.java)
if (!extension.enablePrivacy) {
return // 功能关闭时直接返回
}
// 2. 只对 App 插件生效
if (project.plugins.hasPlugin(AppPlugin::class.java)) {
// 3. 清空历史数据
HookMethodManager.MANAGER.clear()
HookFieldManager.MANAGER.clear()
// 4. 注册 Transform
registerTransform(project)
// 5. 设置 Manifest 和 Assets 处理任务
setupTasks(project)
}
}
}
3.2 两阶段 Transform 架构
编译期流程:
┌──────────────────────────────────┐
│ 输入:所有 .class 文件和 Jar │
└──────────────┬───────────────────┘
│
┌──────▼─────────────┐
│ 第一阶段(预处理) │
│ Pre-Transform │
└──────┬─────────────┘
│
┌──────────┴──────────────┐
│ │
┌───▼────────────────┐ ┌───▼──────────────────┐
│MethodProxy │ │ ClassProxy │
│ CollectTransform │ │ CollectTransform │
│ (收集方法拦截规则) │ │ (收集类替换规则) │
└───┬────────────────┘ └───┬──────────────────┘
│ │
│ 结果写入: │
│ HookMethodManager │ ReplaceClassManager
│
┌──────▼────────────────┐
│ 第二阶段(执行替换) │
│ Transform │
└──────┬────────────────┘
│
┌──────────┼──────────────┬──────────────┐
│ │ │ │
┌───▼───┐ ┌───▼───┐ ┌───────▼──┐ ┌────────▼───┐
│Method │ │Field │ │ Class │ │ Service │
│Hook │ │Proxy │ │ Proxy │ │ Hook │
└───┬───┘ └───┬───┘ └───────┬──┘ └────────┬───┘
│ │ │ │
└─────────┴─��──────────┴─────────────┘
│
┌──────▼──────────┐
│ FlushHookData │
│ Transform │
└────────┬────────┘
│
┌────────────▼──────────────┐
│ 输出:修改后的 .class 和 │
│ privacy_hook.json │
└───────────────────────────┘
3.3 字节码替换原理
原始代码:
ActivityManager manager = ...;
List<RunningTaskInfo> tasks = manager.getRunningTasks(maxNum);
原始字节码:
ALOAD 1 // 加载 manager 对象
ILOAD 2 // 加载 maxNum 参数
INVOKEVIRTUAL ActivityManager.getRunningTasks
替换后的字节码:
ALOAD 1 // 加载 manager 对象
ILOAD 2 // 加载 maxNum ��数
INVOKESTATIC PrivacyProxyCall$Proxy.getRunningTasks
第四部分:Hook 层详细分析
4.1 运行时 SDK 初始化
文件: hook-sentry/src/main/java/com/yl/lib/sentry/hook/PrivacySentry.kt
⚠️ 初始化时机: 建议在 Application.attachBaseContext() 中第一个调用
class MyApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
// 必须最先初始化
PrivacySentry.Privacy.init(
this,
PrivacySentryBuilder()
.enableFileResult(true)
.syncDebug(true)
.configWatchTime(30 * 60 * 1000)
)
}
override fun onCreate() {
super.onCreate()
// 隐私协议确认后调用
showPrivacyDialog {
PrivacySentry.Privacy.updatePrivacyShow()
}
}
}
4.2 三层缓存架构
┌───────────────────────────────────────────┐
│ CachePrivacyManager(统一入口) │
└───────────────┬─────────────────────────────┘
│
┌───────────┼───────────┬───────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Memory │ │TimeLess │ │Permanent │ │TimeLess │
│ Cache │ │Memory │ │ Disk │ │ Disk │
│ │ │Cache │ │ Cache │ │ Cache │
└────┬────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
└───────────┴────────────┴───────────┘
│
┌──────────▼──────────┐
│ SharedPreferences │
│ (磁盘持久化) │
└─────────────────────┘
4.3 缓存类型和使用场景
| 缓存类型 |
生命周期 |
使用场景 |
实现类 |
| 内存缓存 |
进程级别 |
IMEI、IMSI、MEID、Serial 等设备标识 |
MemoryCache |
| 时效内存缓存 |
时间限制 |
位置信息、WiFi 信息 |
TimeLessMemoryCache |
| 永久磁盘缓存 |
持久化 |
Android ID、设备名等不可变字段 |
DiskCache |
| 时效磁盘缓存 |
磁盘+时效 |
需要定期刷新的敏感信息 |
TimeLessDiskCache |
第五部分:代理层详细分析
5.1 支持的敏感 API 类别
| 类别 |
API 示例 |
��截方式 |
| 任务管理 |
ActivityManager.getRunningTasks() |
返回空列表/代理 |
| 剪贴板 |
ClipboardManager.getPrimaryClip() |
代理访问 |
| 蓝牙 |
BluetoothAdapter.getAddress() |
代理访问 |
| WiFi |
WifiManager.getConnectionInfo() |
代理访问 |
| 位置 |
LocationManager.getLastKnownLocation() |
缓存+代理 |
| 设备标识 |
Build.getSerial() |
缓存+代理 |
| 应用列表 |
PackageManager.getInstalledPackages() |
返回空列表 |
| 通话信息 |
TelephonyManager.getDeviceId() |
缓存+代理 |
| 传感器 |
SensorManager.registerListener() |
记录日志 |
| 权限 |
requestPermissions() |
记录日志 |
5.2 代理实现示例
@PrivacyClassProxy
object PrivacyProxyCall {
@PrivacyMethodProxy(
originalClass = ActivityManager::class,
originalMethod = "getRunningTasks",
originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL
)
@JvmStatic
fun getRunningTasks(
manager: ActivityManager,
maxNum: Int
): List<RunningTaskInfo> {
doFilePrinter("getRunningTasks", "当前运行中的任务")
// 核心逻辑:根据隐私协议状态决定返回值
if (PrivacySentry.Privacy.inDangerousState()) {
return emptyList() // 危险状态返回空
}
return manager.getRunningTasks(maxNum)
}
}
第六部分:完整数据流分析
6.1 从注解定义到字节码替换的完整流程
开发阶段
────────────────────────────────
│
1️⃣ 开发者编写代理方法
├─ 使用 @PrivacyClassProxy 标注类
├─ 使用 @PrivacyMethodProxy 标注方法
└─ 编译生成 class 文件(带注解元数据)
│
▼
编译期间
────────────────────────────────
│
2️⃣ Gradle 执行 PrivacySentryPlugin
├─ 清空历史 Manager
└─ 注册 Transform 任务
│
▼
3️⃣ 第一阶段:预处理 (Pre-Transform)
├─ MethodProxyCollectTransform
│ ├─ 扫描所有 class 文件
│ ├─ 找到 @PrivacyClassProxy 的类
│ ├─ 提取 @PrivacyMethodProxy 注解信息
│ └─ 存储到 HookMethodManager
│
└─ ClassProxyCollectTransform
└─ 存储到 ReplaceClassManager
│
▼
4️⃣ 第二阶段:执行替换 (Transform)
├─ MethodHookTransform
│ ├─ 遍历应用代码中的所有方法调用
│ ├─ 在 HookMethodManager 中查询
│ ├─ 修改字节码调用
│ └─ 记录到 HookedDataManger
│
└─ FlushHookDataTransform
└─ 生成 privacy_hook.json
│
▼
运行期间
────────────────────────────────
│
5️⃣ 应用启动 → SDK 初始化
└─ 用户同意隐私协议
└─ 业务代码调用敏感 API
└─ 执行代理方法
└─ 记录日志并输出
第七部分:关键交互关系
7.1 模块间的依赖关系
privacy-annotation (底层)
↑
│ 依赖(注解定义)
│
plugin-sentry ◄──── Booster 框架
│ ASM 库
│ 生成
▼
privacy_hook.json
│
▼ 加载
应用 APK
│
├─ 包含修改后的 bytecode
└─ 包含 privacy_hook.json
│
▼
hook-sentry (运行时)
│ 依赖
├─ privacy-annotation
└─ privacy-proxy
第八部分:高级特性
8.1 反射 Hook
支持拦截通过反射方式调用的敏感方法:
privacy {
hookReflex = true
reflexMap = [
"com.android.id.impl.IdProviderImpl": [
"getOAID", // 小米设备的广告 ID
"getAAID",
"getVAID"
]
]
}
8.2 多进程支持
自动为不同进程生成独立的日志文件:
// 结果文件位置
/storage/emulated/0/Android/data/{packageName}/files/privacy/
├─ privacy_result.xls (主进程)
├─ com.example.service_result.xls (Service 进程)
└─ com.example.worker_result.xls (其他进程)
8.3 粘性数据
粘性数据 (Sticky Data): 处理 SDK 初始化晚于敏感 API 调用的场景,确保不遗漏任何调用记录。
8.4 黑名单配置
privacy {
blackList = [
"com.loc", // 高德地图(ASM 冲突)
"com.amap.api",
"io.openinstall.sdk"
]
}
第九部分:生成的产物分析
9.1 privacy_hook.json 结构
{
"hookServiceList": [
"com.example.TestService"
],
"replaceMethodMap": {
"android.app.ActivityManager.getRunningAppProcesses": {
"count": 3,
"originMethodList": [...]
}
}
}
9.2 Excel 输出文件
文件位置: /storage/emulated/0/Android/data/{packageName}/files/privacy/
Sheet 1:隐私合规明细
| 调用时间 |
别名 |
函数名 |
调用堆栈 |
| 2024-12-30 10:25:15 |
当前运行中的任务 |
getRunningTasks |
com.example.MainActivity.onCreate() ... |
Sheet 2:调用次数统计
| 别名 |
函数名 |
调用堆栈 |
调用次数 |
| 当前运行中的任务 |
getRunningTasks |
com.example.MainActivity.onCreate() |
3 |
第十部分:完整工作流程示例
示例:拦截 Build.getSerial() 调用
步骤 1:定义代理方法
@PrivacyClassProxy
object PrivacyProxyCall {
@PrivacyMethodProxy(
originalClass = android.os.Build::class,
originalMethod = "getSerial",
originalOpcode = MethodInvokeOpcode.INVOKESTATIC
)
@JvmStatic
fun getSerial(): String? {
doFilePrinter("getSerial", "读取Serial")
if (PrivacySentry.Privacy.inDangerousState()) {
return ""
}
return Build.getSerial()
}
}
步骤 2:编译期收集
MethodProxyCollectTransform 扫描并收集代理方法信息
步骤 3:编译期替换
MethodHookTransform 修改字节码调用
步骤 4:运行时执行
// 1. SDK 初始化
PrivacySentry.Privacy.init(application, builder)
// 2. 用户同意隐私协议
PrivacySentry.Privacy.updatePrivacyShow()
// 3. 业务代码调用
val serial = Build.getSerial() // 实际调用代理方法
第十一部分:架构优势和设计模式
11.1 采用的设计模式
| 模式 |
位置 |
用途 |
| 代理模式 |
privacy-proxy |
代理敏感 API 调用 |
| 工厂模式 |
CachePrivacyManager |
创建不同类型的缓存 |
| 单例模式 |
Manager 对象 |
全局唯一的管理器 |
| 观察者模式 |
PrivacyDataManager |
LiveData 通知数据变化 |
| 策略模式 |
BasePrinter |
不同的输出策略 |
11.2 关键架构优势
✅ 核心优势
- 编译期处理:相比 Xposed 不需要 Root,零运行时 overhead
- 完整方案:识别 → 拦截 → 记录 → 报告的完整链路
- 灵活扩展:支持自定义代理、黑名单、反射拦截
- 高效缓存:三层缓存策略满足不同场景
- 多进程支持:自动处理多进程数据隔离
第十二部分:使用建议和最佳实践
12.1 集成步骤
1. 在根 build.gradle 中添加插件依赖
buildscript {
dependencies {
classpath 'com.github.allenymt.PrivacySentry:plugin-sentry:1.3.7_v820_beta4'
}
}
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
2. 在 App build.gradle 中应用插件
apply plugin: 'privacy-sentry-plugin'
dependencies {
implementation "com.github.allenymt.PrivacySentry:hook-sentry:1.3.7_v820_beta4"
implementation "com.github.allenymt.PrivacySentry:privacy-annotation:1.3.7_v820_beta4"
implementation "com.github.allenymt.PrivacySentry:privacy-proxy:1.3.7_v820_beta4"
}
privacy {
enablePrivacy = true
blackList = []
}
3. 初始化 SDK
class MyApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
PrivacySentry.Privacy.init(
this,
PrivacySentryBuilder()
.syncDebug(true)
.enableFileResult(true)
.configWatchTime(30 * 60 * 1000)
)
}
}
12.2 调试技巧
- 查看 Logcat:
adb logcat | grep "PrivacyOfficer"
- 拉取文件:
adb pull /storage/emulated/0/Android/data/{packageName}/files/privacy/
- 查看配置:
cat privacy_hook.json | jq
12.3 性能考量
| 指标 |
影响 |
| 编译时间 |
增加 < 2 秒 |
| APK 包体积 |
增加 ~200KB |
| 运行时性能 |
零额外开销 |
| 内存占用 |
缓存数据占用,可定期清理 |
12.4 常见问题
Q: 为什么某些敏感 API 调用没有被拦截?
A: 检查以下几点:
- 是否添加了代理实现
- 检查黑名单配置
- 检查是否同意了隐私协议
- 查看 privacy_hook.json 是否包含该 API
Q: 为什么应用闪退?
A: 可能原因:
- privacy-proxy 中的代理实现有 bug
- ASM 库版本冲突(特别是高德地图)
- 尝试添加到黑名单
总结
PrivacySentry 是一套设计精妙的隐私合规解决方案,通过以下特点提供完整的隐私检测能力:
- ✅ 四层架构 - 从注解定义到运行时拦截的完整链路
- ✅ 两阶段 Transform - 先收集再替换,模块化清晰
- ✅ 智能缓存 - 多种缓存策略满足不同场景
- ✅ 完整日志 - Excel 报表化的统计分析
- ✅ 隐私协议状态管理 - 根据用户同意状态动态返回数据
- ✅ 多进程支持 - 自动处理多进程的数据隔离
关键数据流:注解 → 编译期收集 → 字节码替换 → 运行时拦截 → 日志收集 → Excel 输出