背景
近年来,工信部对APP个人隐私要求越来越多,之后各大应用市场也开始要求,有违规情况的会导致APP下架或者无法上架。这不,我的app就因为三方SDK频繁获取Android ID 导致无法上架,等SDK厂商更新也很浪费时间,所以只能想办法去处理这件事,好在找到了PrivacySentry这个神器,可规避应用市场上架合规检测的大部分问题
如何使用
一、配置PrivacySentry
第一步:在根目录的build.gradle
中添加依赖
首先,你需要在项目的根目录build.gradle
文件中添加以下配置:
gradle复制代码
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
buildscript { dependencies { // 添加PrivacySentry插件依赖 classpath 'com.github.allenymt.PrivacySentry:plugin-sentry:1.3.4.2' } }
注意:这里我们添加了jitpack.io
作为Maven仓库,以便能够下载到PrivacySentry的相关依赖。
第二步:在项目中的build.gradle
中添加插件和依赖
接下来,你需要在主项目的build.gradle
文件中添加以下配置:
gradle复制代码
// 在主项目里添加PrivacySentry插件依赖 apply plugin: 'privacy-sentry-plugin'
dependencies { // aar依赖 def privacyVersion = "1.3.4.2" implementation "com.github.allenymt.PrivacySentry:hook-sentry:$privacyVersion" implementation "com.github.allenymt.PrivacySentry:privacy-annotation:$privacyVersion"
// 代理类的库,如果自己没有代理类,那么必须引用这个aar!! // 如果不想使用库中本身的代理方法,可以不引入这个aar,但是自己必须实现代理类!! // 引入privacy-proxy,也可以自定义类代理方法,优先以业务方定义的为准 implementation "com.github.allenymt.PrivacySentry:privacy-proxy:$privacyVersion" // 1.2.3 新增类替换,主要是为了hook构造函数的参数,按业务方需求自己决定 implementation "com.github.allenymt.PrivacySentry:privacy-replace:$privacyVersion" }
// 隐私配置 privacy { // 设置免hook的名单 blackList = [] // 开关PrivacySentry插件功能,核心功能开关,默认为true enablePrivacy = true
// 开启hook反射的方法,默认为false,按需打开 hookReflex = false // 配置反射拦截 反射获取小米系统的oaid、aaid、vaid,例如极光、个推、穿山甲等SDK都有获取 reflexMap = ["com.android.id.impl.IdProviderImpl":["getOAID","getAAID","getVAID"]]
// 默认为false,按需打开 hookConstructor = false // 默认为false,按需打开 hookField = false
//*************以下是分割线,主要是对Service的自启动优化处理,默认为false,按需打开**************** // 处理Manifest文件,主要是处理Service的Priority , 关闭Service的Export enableProcessManifest = false // hook Service的部分代码,修复在MIUI上的自启动问题 // 部分Service把自己的Priority设置为1000,这里开启代理功能,可以代理成0 enableReplacePriority = false replacePriority = 1
// 支持关闭Service的Export功能,默认为false,注意部分厂商通道之类的push(xiaomi、vivo、huawei等厂商的pushService),不能关闭 enableCloseServiceExport = false // Export白名单Service serviceExportPkgWhiteList = ["white"] enableHookServiceStartCommand = false }
二、初始化PrivacySentry
在完成上述配置后,你需要在应用中进行PrivacySentry的初始化。这里需要注意的是,初始化方法最好在attachBaseContext
中第一个调用(从1.3.1版本开始,晚点初始化也不影响检测结果)。
java复制代码
// 初始化PrivacySentry PrivacySentryBuilder builder = new PrivacySentryBuilder() // 自定义文件结果的输出名 .configResultFileName("buyer_privacy") // debug打开,可以看到logcat的堆栈日志 .syncDebug(true) // 配置写入文件日志 , 线上包这个开关不要打开!!!!,true打开文件输入,false关闭文件输入 .enableFileResult(true) // 持续写入文件30分钟 .configWatchTime(30 * 60 * 1000) // 文件输出后的回调 .configResultCallBack(new PrivacyResultCallBack() { @Override public void onResultCallBack(@NonNull String s) { // 回调处理 } });
// 添加默认结果输出,包含log输出和文件输出 PrivacySentry.Privacy.INSTANCE.init(application, builder);
三、隐私协议确认
在应用中,隐私协议的确认是至关重要的一步。如果在日志中发现“check!!! 还未展示隐私协议,Illegal print”等提示信息,说明此时用户还未同意隐私协议,但应用已经调用了敏感或违规的API。因此,在隐私协议确认时,你需要调用以下方法来告知SDK:
kotlin复制代码
// Kotlin语法 PrivacySentry.Privacy.updatePrivacyShow()
java复制代码
// Java语法 PrivacySentry.Privacy.INSTANCE.updatePrivacyShow();
这一步非常重要,因为它能够确保SDK在检测到隐私协议已经同意后,再对后续的敏感操作进行监控和防护。
支持自定义配置hook函数 /** * @author yulun * @since 2022-01-13 17:57 * 主要是两个注解PrivacyClassProxy和PrivacyMethodProxy,PrivacyClassProxy代表要解析的类,PrivacyMethodProxy代表要hook的方法配置 */ @Keep open class PrivacyProxyResolver { // kotlin里实际解析的是这个PrivacyProxyCall$Proxy 内部类 @PrivacyClassProxy @Keep object Proxy { // 查询 @SuppressLint("MissingPermission") @PrivacyMethodProxy( originalClass = ContentResolver::class, // hook的方法所在的类名 originalMethod = "query", // hook的方法名 originalOpcode = MethodInvokeOpcode.INVOKEVIRTUAL //hook的方法调用,一般是静态调用和实例调用 ) @JvmStatic fun query( contentResolver: ContentResolver?, //实例调用的方法需要把声明调用对象,我们默认把对象参数放在第一位 uri: Uri, projection: Array<String?>?, selection: String?, selectionArgs: Array<String?>?, sortOrder: String? ): Cursor? { doFilePrinter("query", "查询服务: ${uriToLog(uri)}") // 输入日志到文件 return contentResolver?.query(uri, projection, selection, selectionArgs, sortOrder) } @RequiresApi(Build.VERSION_CODES.O) @PrivacyMethodProxy( originalClass = android.os.Build::class, originalMethod = "getSerial", originalOpcode = MethodInvokeOpcode.INVOKESTATIC //静态调用 ) @JvmStatic fun getSerial(): String? { var result = "" try { doFilePrinter("getSerial", "读取Serial") if (PrivacySentry.Privacy.getBuilder()?.isVisitorModel() == true) { return "" } result = Build.getSerial() } catch (e: Exception) { e.printStackTrace() } return result } } }
支持多进程,多进程产出的文件名前缀默认增加进程名
如何配置替换一个类 可以参考源码中PrivacyFile的配置,使用PrivacyClassReplace注解,originClass代表你要替换的类,注意要继承originClass的所有构造函数 可以配置 hookConstructor = false关闭这个功能 /** * @author yulun * @since 2022-11-18 15:01 * 代理File的构造方法,如果是自定义的file类,需要业务方单独配置自行处理 */ @PrivacyClassReplace(originClass = File.class) public class PrivacyFile extends File { public PrivacyFile(@NonNull String pathname) { super(pathname); record(pathname); } public PrivacyFile(@Nullable String parent, @NonNull String child) { super(parent, child); record(parent + child); } public PrivacyFile(@Nullable File parent, @NonNull String child) { super(parent, child); record(parent.getPath() + child); } public PrivacyFile(@NonNull URI uri) { super(uri); record(uri.toString()); } private void record(String path) { PrivacyProxyUtil.Util.INSTANCE.doFilePrinter("PrivacyFile", "访问文件", "path is " + path, PrivacySentry.Privacy.INSTANCE.getBuilder().isVisitorModel(), false); } }
隐私方法调用结果产出
支持hook调用堆栈至文件,默认的时间为1分钟,支持自定义设置时间。
排查结果可参考目录下的demo_result.xls,排查结果支持两个维度查看,第一是结合隐私协议的展示时机和敏感方法的调用时机,第二是统计所有敏感函数的调用次数
排查结果可观察日志,结果文件会在 /storage/emulated/0/Android/data/yourPackgeName/files/xx.xls,需要手动执行下adb pull
logcat日志查看:TAG名为PrivacyOfficer
基本原理
编译期注解+hook方案,第一个transform收集需要拦截的敏感函数,第二个transform替换敏感函数,运行期收集日志
为什么不用xposed等框架? 因为想做本地自动化定期排查,第三方hook框架外部依赖性太大
为什么不搞基于lint的排查方式? 工信部对于运行期 敏感函数的调用时机和次数都有限制,代码扫描解决不了这些问题
支持的hook函数列表
支持hook以下功能函数:
支持敏感字段缓存(磁盘缓存、带有时间限制的磁盘缓存、内存缓存)
hook替换类 (构造函数)
当前运行进程和任务
系统剪贴板服务
读取设备应用列表
读取 Android SN(Serial,包括方法和变量),系统设备号
读写联系人、日历、本机号码
获取定位、基站信息、wifi信息
Mac 地址、IP 地址
读取 IMEI(DeviceId)、MEID、IMSI、ADID(AndroidID)
手机可用传感器,传感器注册,传感器列表
权限请求
常见的合规字段整理
IMEI、MAC地址、MEID、IMSI、SN、ICCID等设备唯一标识符,Android ID、WiFi(WiFi名称、WiFi MAC地址以及设备扫描到的所有WiFi信息),SIM卡信息(IMSI、SIM卡序列号ICCID、手机号、运营商信息),应用安装列表(设备所有已安装应用的包名和应用名),传感器(传感器列表、加速度传感器、温度传感器等),蓝牙信息(设备蓝牙地址和设备扫描到的蓝牙设备信息),基站定位、GPS(用户地理位置信息),账户(各类应用注册的不同账号信息)、剪切板、IP地址、硬件序列号、SDCard信息(公有目录)
来源:
互联网
本文观点不代表码客-全球程序员交流社区立场,不承担法律责任,文章及观点也不构成任何投资意见。
评论列表