支持扫描隐私权限方法,替换隐私权限方法等功能。
buildscript {
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.github.coofee:RewritePlugin:<latest version>'
}
}
apply plugin: 'com.coofee.rewrite'
rewrite {
scanPermissionMethodCaller {
// ...
}
replaceMethod {
// ...
}
}
注意执行插件前,需要先clean
,否会因为缓存使用旧结果。
$ ./gradlew clean
$ ./gradlew :app:assembleDebug
首先需要保证已通过sdk manager
安装对应compileSdkVersion
版本的android源代码**,如下图所示:
然后通过执行collectAndroidPermissionMethod
任务获取android framework中需要权限的方法集:
$ ./gradlew :app:collectAndroidPermissionMethod
[RewritePlugin] success write to file=app/android_framework_class_method_permission.json
查看android_framework_class_method_permission.json
文件的内容格式大致如下:
{
"android.telephony.TelephonyManager#getDeviceId": [
"android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE"
],
"android.telephony.TelephonyManager#getImei": [
"android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE"
],
"android.telephony.TelephonyManager#getMeid": [
"android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE"
]
}
配置scanPermissionMethodCaller
如下所示:
注意:对于使用
ContentResolver
的获取联系人/短信
等需要权限的行为,暂时不支持通过scanPermissionMethodCaller
统计。 可以参考ShadowContentResolver.java和replace_method的配置, 使用replaceMethod
进行运行时拦截,统计权限获取情况。
scanPermissionMethodCaller {
// 会在 replaceMethod 之后执行,所以在 replaceMethod 中配置的方法不会被统计到。
enable = true
// 配置未添加权限的方法,比如隐私方法等。
configPermissionMethods = [
"android.content.pm.PackageManager#getInstalledPackages" : [
"获取应用列表"
] as Set,
"android.content.pm.PackageManager#getInstalledApplications" : [
"获取应用列表"
] as Set,
// 高德地图
"com.amap.api.location.AMapLocationClient#startLocation" : [
"android.Manifest.permission.ACCESS_FINE_LOCATION",
] as Set,
"com.amap.api.location.AMapLocationClient#startAssistantLocation": [
"android.Manifest.permission.ACCESS_FINE_LOCATION",
] as Set,
// 百度地图
"com.baidu.location.LocationClient#start" : [
"android.Manifest.permission.ACCESS_FINE_LOCATION",
] as Set,
"android.net.wifi.WifiInfo#getMacAddress" : [
"Mac地址"
] as Set,
"java.net.NetworkInterface#getHardwareAddress" : [
"Mac地址"
] as Set,
]
// 当执行 `collectAndroidPermissionMethod` 任务成功后,
// 会生成 android_framework_class_method_permission.json 到当前project目录中.
configFile = file("android_framework_class_method_permission.json")
// 输出扫描结果
outputFile = file("scan_permission_method_caller.json")
// 忽略android系统库
excludes = [
"android/", "java/", "javax/", 'com/coofee/rewrite/hook/'
]
}
然后通过如下命令编译应用:
$ ./gradlew :app:assembleDebug
[RewritePlugin] scan permission method caller result success write to file app/scan_permission_method_caller_result.json
[RewritePlugin] scan permission method caller result success write to file app/scan_permission_method_caller_result_by_module.json
scan_permission_method_caller_result.json
扫描结果文件是按照权限进行分组,样例如下:
{
"android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE": [
{
"moduleName": "8f0a287af65fe4840370804c25783e3d59e2e135",
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetDeviceId",
"lineNo": 113,
"permissionMethod": "android.telephony.TelephonyManager#getDeviceId"
}
],
"Mac地址": [
{
"moduleName": "8f0a287af65fe4840370804c25783e3d59e2e135",
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetMacAddress",
"lineNo": 122,
"permissionMethod": "android.net.wifi.WifiInfo#getMacAddress"
},
{
"moduleName": "8f0a287af65fe4840370804c25783e3d59e2e135",
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetMacAddress",
"lineNo": 126,
"permissionMethod": "java.net.NetworkInterface#getHardwareAddress"
}
]
}
scan_permission_method_caller_result_by_module.json
扫描结果文件则是按照模块名进行分组,样例如下:
{
"androidx.appcompat:appcompat:1.1.0": [
{
"className": "androidx.appcompat.app.TwilightManager",
"methodName": "getLastKnownLocationForProvider",
"lineNo": 135,
"permissionMethod": "android.location.LocationManager#getLastKnownLocation",
"permissions": [
"android.Manifest.permission.ACCESS_FINE_LOCATION",
"android.Manifest.permission.ACCESS_COARSE_LOCATION"
]
}
],
"8f0a287af65fe4840370804c25783e3d59e2e135": [
{
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetDeviceId",
"lineNo": 113,
"permissionMethod": "android.telephony.TelephonyManager#getDeviceId",
"permissions": [
"android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE"
]
},
{
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetMacAddress",
"lineNo": 122,
"permissionMethod": "android.net.wifi.WifiInfo#getMacAddress",
"permissions": [
"Mac地址"
]
},
{
"className": "com.coofee.rewrite.MainActivity",
"methodName": "testGetMacAddress",
"lineNo": 126,
"permissionMethod": "java.net.NetworkInterface#getHardwareAddress",
"permissions": [
"Mac地址"
]
}
]
}
在replace_method.json
文件中配置要替换的方法,编译时会使用dest_class.dest_method
替换src_class.src_method
对应的方法。
replaceMethod {
// 启用后编译应用时会替换除`excludes`包之外的其他类的方法,可以根据`[RewritePlugin] replace`关键字过滤输出日志获取结果。
enable = true
// 不替换该包下的方法
excludes = [
'com/coofee/rewrite/hook/'
]
// 配置要替换的方法
configFile = file("replace_method.json")
}
replace_method.json
文件是json数组,其结构如下所示,替换隐私API的方法配置详见:replace_method.json
[
{
"src_class": "android.telephony.TelephonyManager",
"dest_class": "com.coofee.rewrite.hook.telephony.ShadowTelephoneManager",
"methods": [
{
"src_method": "java.lang.String getDeviceId()",
"dest_method": "java.lang.String getDeviceId(android.telephony.TelephonyManager)"
},
{
"src_method": "java.lang.String getDeviceId(int)",
"dest_method": "java.lang.String getDeviceId(android.telephony.TelephonyManager, int)"
}
]
}
]
- 结果查看:
可以使用[RewritePlugin] replace
关键字过滤输出日志获取替换结果,单条输出如下所示:
[RewritePlugin] replace moduleName=8f0a287af65fe4840370804c25783e3d59e2e135, sourceFile=MainActivity.kt, lineNo=61, className=com.coofee.rewrite.MainActivity, methodName=testPackageManager, methodDesc=()V, methodSignature=null; owner=android/content/pm/PackageManager, method=getInstalledApplications, desc=(I)Ljava/util/List; by owner=com/coofee/rewrite/hook/pm/ShadowPackageManager, method=getInstalledApplications, desc=(Landroid/content/pm/PackageManager;I)Ljava/util/List;
replace_method
编写方法
- 如果
src_method
是实例方法,则其对应的dest_method
静态方法的第一个参数是实例自身,也就是this
。 - 如果
src_method
是静态方法,则其对应的dest_method
静态方法和其一模一样。 - 如果
src_method
方法存在泛型,在需要去掉其限定类型,见:PackageManager配置。 - 如果
src_class
是内部类,包名的分隔符则需要使用$
而不是.
,见:android.provider.Settings$System
配置。
配置例子详见:replace_method.json
当有模块是dynamic-feature
时,需要将application
模块中的minifyEnabled
必须设置为true,同时在dynamic-feature
模块中引入插件配置,否则插件对dynamic-feature
模块不生效。
详见:app
和loader
模块的配置,其中loader
是dynamic-feature
模块。
$ ./gradlew :app:bundleDebug --info
RewritePlugin is licensed under the Apache License 2.0.