├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── privacy_methods.json
│ ├── java
│ └── com
│ │ └── lanshifu
│ │ └── privacymethodhooker
│ │ ├── MainActivity.kt
│ │ ├── test
│ │ └── JaveTest.java
│ │ ├── testcase
│ │ └── TopLevelUtil.kt
│ │ └── utils
│ │ ├── PrivacyUtil.kt
│ │ ├── ReflectUtils.kt
│ │ └── Test.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── content_main.xml
│ ├── fragment_first.xml
│ └── fragment_second.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── navigation
│ └── nav_graph.xml
│ ├── values-land
│ └── dimens.xml
│ ├── values-night
│ └── themes.xml
│ ├── values-w1240dp
│ └── dimens.xml
│ ├── values-w600dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── themes.xml
├── asm_annotation
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── lanshifu
│ └── asm_annotation
│ ├── AsmMethodOpcodes.kt
│ └── AsmMethodReplace.java
├── build.gradle
├── buildSrc
├── build.gradle
└── src
│ └── main
│ ├── kotlin
│ └── com
│ │ └── lanshifu
│ │ └── plugin
│ │ ├── DelegateTransformInvocation.kt
│ │ ├── Plugin.kt
│ │ ├── asmtransformer
│ │ └── BaseAsmTransformer.kt
│ │ ├── classtransformer
│ │ ├── AbsClassTransformer.kt
│ │ ├── AnnotationParserClassTransform.kt
│ │ └── PrivacyMethodReplaceTransform.kt
│ │ ├── extension
│ │ └── CommonExt.kt
│ │ ├── privacymethod
│ │ └── AsmItem.kt
│ │ └── transform
│ │ ├── BaseTransform.kt
│ │ ├── CommonTransform.kt
│ │ └── PrivacyMethodHookTransform.kt
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── com.lanshifu.privacy_method.properties
├── config.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | .gradle
4 | /local.properties
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
18 | # Project exclude paths
19 | /buildSrc/build/
20 | /buildSrc/build/classes/java/main/
21 | /buildSrc/build/classes/java/test/
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PrivacyMethodHooker
2 | 关联文章:
3 | [ASM hook隐私方法调用,防止App被下架](https://juejin.cn/post/7043399520486424612#heading-0)
4 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | apply plugin: 'kotlin-android-extensions'
5 | apply plugin: 'kotlin-kapt'
6 |
7 | apply plugin: 'com.lanshifu.privacy_method'
8 |
9 | android {
10 | compileSdkVersion rootProject.ext.version["compileSdkVersion"]
11 | defaultConfig {
12 | applicationId "com.lanshifu.privacymethodhooker"
13 | minSdkVersion rootProject.ext.version["minSdkVersion"]
14 | targetSdkVersion rootProject.ext.version["targetSdkVersion"]
15 | versionCode rootProject.ext.version["versionCode"]
16 | versionName rootProject.ext.version["versionName"]
17 | multiDexEnabled true
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | }
20 |
21 | sourceSets {
22 | main {
23 | jniLibs.srcDirs = ['libs']
24 | }
25 | }
26 |
27 | buildTypes {
28 | release {
29 | minifyEnabled false
30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 |
34 | buildFeatures {
35 | dataBinding = true
36 | }
37 |
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_11
40 | targetCompatibility JavaVersion.VERSION_11
41 | }
42 |
43 | kotlinOptions {
44 | jvmTarget = '11'
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation 'androidx.core:core-ktx:1.8.0'
50 | implementation 'androidx.appcompat:appcompat:1.5.0'
51 | implementation 'com.google.android.material:material:1.6.1'
52 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
53 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
54 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
55 | testImplementation 'junit:junit:4.13.2'
56 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
58 | implementation project(path: ':asm_annotation')
59 | }
60 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/assets/privacy_methods.json:
--------------------------------------------------------------------------------
1 | {
2 | "methods": [
3 | {
4 | "name_regex": "android.app.ActivityManager.getRunningAppProcesses",
5 | "message": "读取当前运行应用进程"
6 | },
7 | {
8 | "name_regex": "android.telephony.TelephonyManager.listen",
9 | "message": "监听呼入电话信息"
10 | },
11 | {
12 | "name_regex": "android.app.ActivityManager.getRecentTasks",
13 | "message": "读取最近运行任务"
14 | },
15 | {
16 | "name_regex": "android.app.ActivityManager.getRunningTasks",
17 | "message": "读取当前运行任务"
18 | },
19 | {
20 | "name_regex": "android.telephony.TelephonyManager.getAllCellInfo",
21 | "message": "读取基站信息"
22 | },
23 | {
24 | "name_regex": "android.location.LocationManager.getLastKnownLocation",
25 | "message": "读取精细位置信息"
26 | },
27 | {
28 | "name_regex": "android.location.LocationManager.getLastLocation",
29 | "message": "读取粗略位置信息"
30 | },
31 | {
32 | "name_regex": "android.location.LocationManager.requestLocationUpdates",
33 | "message": "监视精细行动轨迹"
34 | },
35 | {
36 | "name_regex": "android.media.AudioRecord.native_start",
37 | "message": "音频录制"
38 | },
39 | {
40 | "name_regex": "android.telephony.TelephonyManager.getDeviceId",
41 | "message": "读取DEVICEID"
42 | },
43 | {
44 | "name_regex": "android.net.wifi.WifiInfo.getSSID",
45 | "message": "读取WIFI的SSID"
46 | },
47 | {
48 | "name_regex": "android.net.wifi.WifiInfo.getBSSID",
49 | "message": "读取WIFI的BSSID"
50 | },
51 | {
52 | "name_regex": "android.hardware.SensorManager.getSensorList",
53 | "message": "读取传感器列表"
54 | },
55 | {
56 | "name_regex": "android.hardware.SensorManager.registerListenerImpl",
57 | "message": "监视传感器"
58 | },
59 | {
60 | "name_regex": "android.net.wifi.WifiManager.getConfiguredNetworks",
61 | "message": "读取已配置WIFI信息"
62 | },
63 | {
64 | "name_regex": "android.telephony.TelephonyManager.getSubscriberId",
65 | "message": "读取IMSI"
66 | },
67 | {
68 | "name_regex": "android.telephony.TelephonyManager.getSimSerialNumber",
69 | "message": "读取ICCID"
70 | },
71 | {
72 | "name_regex": "android.net.wifi.WifiInfo.getMacAddress",
73 | "message": "读取MAC地址"
74 | },
75 | {
76 | "name_regex": "android.telephony.TelephonyManager.getImei",
77 | "message": "读取IMEI"
78 | },
79 | {
80 | "name_regex": "android.net.wifi.WifiManager.getScanResults",
81 | "message": "读取WIFI扫描结果"
82 | },
83 | {
84 | "name_regex": "android.net.wifi.WifiManager.getDhcpInfo",
85 | "message": "读取DHCP信息"
86 | },
87 | {
88 | "name_regex": "android.app.AlarmManager.setImpl",
89 | "message": "定时启动->AlarmManager.setImpl"
90 | },
91 | {
92 | "name_regex": "android.provider.Settings$Secure.getStringForUser",
93 | "message": "读取AndroidID"
94 | },
95 | {
96 | "name_regex": "android.content.ContentResolver.registerContentObserver",
97 | "message": "registerContentObserver ->监视多媒体文件"
98 | }
99 | ],
100 | "constructions": [
101 | {
102 | "name_regex": "",
103 | "message": ""
104 | }
105 | ]
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker
2 |
3 | import android.os.Build
4 | import android.os.Bundle
5 | import android.provider.Settings
6 | import androidx.annotation.RequiresApi
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.lanshifu.privacymethodhooker.testcase.*
9 | import com.lanshifu.privacymethodhooker.utils.PrivacyUtil
10 | import kotlinx.android.synthetic.main.activity_main.*
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | @RequiresApi(Build.VERSION_CODES.M)
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 |
18 | setContentView(R.layout.activity_main)
19 |
20 | cbAgree.setOnCheckedChangeListener { compoundButton, b ->
21 | PrivacyUtil.isAgreePrivacy = b
22 | updateData()
23 | }
24 |
25 | cbUseCache.setOnCheckedChangeListener { compoundButton, b ->
26 | PrivacyUtil.isUseCache = b
27 | updateData()
28 | }
29 |
30 | updateData()
31 |
32 | }
33 |
34 | @RequiresApi(Build.VERSION_CODES.M)
35 | private fun updateData() {
36 | val getRunningAppProcesses = getRunningAppProcesses(this)
37 | btnGetRunningAppProcesses.text =
38 | "getRunningAppProcesses size=${getRunningAppProcesses?.size}"
39 |
40 | val getRecentTasks = getRecentTasks(this)
41 | btnGetRecentTasks.text = ("getRecentTasks size=${getRecentTasks?.size}")
42 |
43 | val getRunningTasks = getRunningTasks(this)
44 | btnGetRunningTasks.text = ("getRunningTasks size=${getRunningTasks?.size}")
45 |
46 | val getAllCellInfo = getAllCellInfo(this)
47 | btnGetAllCellInfo.text = ("getAllCellInfo size=${getAllCellInfo?.size}")
48 |
49 | val getDeviceId = getDeviceId(this)
50 | btnGetDeviceId.text = ("getDeviceId=$getDeviceId")
51 |
52 | getSimSerialNumber.text = ("getSimSerialNumber=${getSimSerialNumber(this)}")
53 |
54 | val androidId = Settings.System.getString(this.contentResolver, Settings.Secure.ANDROID_ID)
55 | btnGetIdAndroid.text = ("androidId=$androidId")
56 |
57 | getSSID.text = ("getSSID=${getSSID(this)}")
58 | getBSSID.text = ("getBSSID=${getBSSID(this)}")
59 | getMacAddress.text = ("getMacAddress=${getMacAddress(this)}")
60 | getConfiguredNetworks.text = ("getConfiguredNetworks,size=${getConfiguredNetworks(this)?.size}")
61 |
62 | getSensorList.text = ("getSensorList size=${getSensorList(this)?.size}")
63 | getImei.text = ("getImei=${getImei(this)}")
64 |
65 | getScanResults.text = "getScanResults size=${getScanResults(this)?.size}"
66 | getDhcpInfo.text = "getDhcpInfo=${getDhcpInfo(this)}"
67 |
68 | getLastKnownLocation.text = "getLastKnownLocation=${getLastKnownLocation(this)}"
69 |
70 | requestLocationUpdates(this)
71 | requestLocationUpdates.text = "requestLocationUpdates"
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/test/JaveTest.java:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker.test;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.hardware.Sensor;
6 | import android.hardware.SensorManager;
7 | import android.os.Build;
8 | import android.provider.Settings;
9 |
10 | import androidx.annotation.RequiresApi;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * @author lanxiaobin
16 | * @date 2021/10/10
17 | */
18 | class JaveTest {
19 |
20 | String getDeviceId(Context context) {
21 | ContentResolver cr = context.getContentResolver();
22 | String androidId = Settings.System.getString(cr, Settings.Secure.ANDROID_ID);
23 | return androidId;
24 | }
25 |
26 | @RequiresApi(api = Build.VERSION_CODES.M)
27 | List test(Context context) {
28 | {
29 | SensorManager manager =
30 | (SensorManager) context.getSystemService(SensorManager.class);
31 |
32 | return manager.getSensorList(Sensor.TYPE_ALL);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/testcase/TopLevelUtil.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker.testcase
2 |
3 | import android.Manifest
4 | import android.annotation.SuppressLint
5 | import android.app.Activity
6 | import android.app.ActivityManager
7 | import android.app.ActivityManager.RunningAppProcessInfo
8 | import android.content.Context
9 | import android.content.pm.PackageManager
10 | import android.hardware.Sensor
11 | import android.hardware.SensorManager
12 | import android.location.Location
13 | import android.location.LocationListener
14 | import android.location.LocationManager
15 | import android.net.ConnectivityManager
16 | import android.net.DhcpInfo
17 | import android.net.wifi.ScanResult
18 | import android.net.wifi.WifiConfiguration
19 | import android.net.wifi.WifiInfo
20 | import android.net.wifi.WifiManager
21 | import android.os.Build
22 | import android.os.Bundle
23 | import android.os.Process
24 | import android.telephony.CellInfo
25 | import android.telephony.TelephonyManager
26 | import android.util.Log
27 | import androidx.annotation.RequiresApi
28 | import androidx.core.app.ActivityCompat
29 |
30 | /**
31 | * @author lanxiaobin
32 | * @date 2020-04-25
33 | */
34 |
35 | fun Context.getCurrentProcessName(): String? {
36 | val pid = Process.myPid()
37 | var processName = ""
38 | val manager: ActivityManager =
39 | applicationContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
40 | val runningAppProcesses = manager.runningAppProcesses
41 | for (process in runningAppProcesses) {
42 |
43 | if (process.pid == pid) {
44 | processName = process.processName
45 | }
46 | }
47 | return processName
48 | }
49 |
50 | fun getRunningAppProcesses(context: Context): MutableList? {
51 | val manager: ActivityManager =
52 | context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
53 | return manager.runningAppProcesses
54 | }
55 |
56 | fun getRecentTasks(context: Context): MutableList? {
57 | val manager: ActivityManager =
58 | context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
59 | return manager.getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED)
60 | }
61 |
62 | fun getRunningTasks(context: Context): MutableList? {
63 | val manager: ActivityManager =
64 | context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
65 | return manager.getRunningTasks(100)
66 | }
67 |
68 | @RequiresApi(Build.VERSION_CODES.M)
69 | fun getAllCellInfo(context: Activity): MutableList? {
70 | val manager: TelephonyManager =
71 | context.getSystemService(TelephonyManager::class.java) as TelephonyManager
72 | if (ActivityCompat.checkSelfPermission(
73 | context,
74 | Manifest.permission.ACCESS_FINE_LOCATION
75 | ) != PackageManager.PERMISSION_GRANTED
76 | ) {
77 |
78 | context.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
79 | return null
80 | }
81 | return manager.getAllCellInfo()
82 | }
83 |
84 |
85 | @SuppressLint("HardwareIds")
86 | @RequiresApi(Build.VERSION_CODES.M)
87 | fun getDeviceId(context: Activity): String? {
88 | val manager: TelephonyManager =
89 | context.getSystemService(TelephonyManager::class.java) as TelephonyManager
90 | if (ActivityCompat.checkSelfPermission(
91 | context,
92 | Manifest.permission.READ_PHONE_STATE
93 | ) != PackageManager.PERMISSION_GRANTED
94 | ) {
95 |
96 | context.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0)
97 | return null
98 | }
99 | return manager.getDeviceId()
100 | }
101 |
102 | @RequiresApi(Build.VERSION_CODES.M)
103 | @SuppressLint("HardwareIds")
104 | fun getImei(context: Activity): String? {
105 | val manager: TelephonyManager =
106 | context.getSystemService(TelephonyManager::class.java) as TelephonyManager
107 |
108 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109 | manager.getImei()
110 | } else {
111 | "VERSION.SDK_INT < O"
112 | }
113 | }
114 |
115 | @RequiresApi(Build.VERSION_CODES.M)
116 | fun getSimSerialNumber(context: Activity): String? {
117 | val manager: TelephonyManager =
118 | context.getSystemService(TelephonyManager::class.java) as TelephonyManager
119 | return manager.getSimSerialNumber()
120 | }
121 |
122 |
123 | @RequiresApi(Build.VERSION_CODES.M)
124 | fun getSensorList(context: Activity): MutableList? {
125 | val manager: SensorManager =
126 | context.getSystemService(SensorManager::class.java) as SensorManager
127 |
128 | return manager.getSensorList(Sensor.TYPE_ALL)
129 | }
130 |
131 | private fun getWifiInfo(context: Activity): WifiInfo? {
132 |
133 | if (ActivityCompat.checkSelfPermission(
134 | context,
135 | Manifest.permission.ACCESS_WIFI_STATE
136 | ) != PackageManager.PERMISSION_GRANTED
137 | ) {
138 |
139 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
140 | context.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
141 | return null
142 | }
143 | }
144 |
145 | val connManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
146 | val networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
147 | if (networkInfo != null && networkInfo.isConnected) {
148 | val wifiManager =
149 | context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
150 | return wifiManager.connectionInfo
151 | }
152 | return null
153 | }
154 |
155 | fun getSSID(context: Activity): String? {
156 | return getWifiInfo(context)?.bssid
157 | }
158 |
159 | fun getBSSID(context: Activity): String? {
160 | return getWifiInfo(context)?.bssid
161 | }
162 |
163 | fun getMacAddress(context: Activity): String? {
164 | return getWifiInfo(context)?.macAddress
165 | }
166 |
167 | @RequiresApi(Build.VERSION_CODES.M)
168 | fun getScanResults(context: Activity): MutableList? {
169 | val wifiManager = context.getSystemService(WifiManager::class.java) as WifiManager
170 | if (ActivityCompat.checkSelfPermission(
171 | context,
172 | Manifest.permission.ACCESS_FINE_LOCATION
173 | ) != PackageManager.PERMISSION_GRANTED
174 | ) {
175 | // TODO: Consider calling
176 | // ActivityCompat#requestPermissions
177 | // here to request the missing permissions, and then overriding
178 | // public void onRequestPermissionsResult(int requestCode, String[] permissions,
179 | // int[] grantResults)
180 | // to handle the case where the user grants the permission. See the documentation
181 | // for ActivityCompat#requestPermissions for more details.
182 | return null
183 | }
184 | return wifiManager.scanResults
185 | }
186 |
187 | @RequiresApi(Build.VERSION_CODES.M)
188 | fun getDhcpInfo(context: Activity): DhcpInfo? {
189 | val wifiManager = context.getSystemService(WifiManager::class.java) as WifiManager
190 | return wifiManager.dhcpInfo
191 | }
192 |
193 | @RequiresApi(Build.VERSION_CODES.M)
194 | fun getConfiguredNetworks(context: Activity): MutableList? {
195 | val wifiManager = context.getSystemService(WifiManager::class.java) as WifiManager
196 | if (ActivityCompat.checkSelfPermission(
197 | context,
198 | Manifest.permission.ACCESS_FINE_LOCATION
199 | ) != PackageManager.PERMISSION_GRANTED
200 | ) {
201 | context.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
202 | return null
203 | }
204 | return wifiManager.configuredNetworks
205 | }
206 |
207 | @RequiresApi(Build.VERSION_CODES.M)
208 | fun requestLocationUpdates(context: Activity): Boolean {
209 |
210 | val listener: LocationListener = object : LocationListener {
211 | override fun onLocationChanged(location: Location) {
212 | }
213 |
214 | override fun onProviderDisabled(provider: String) {}
215 | override fun onProviderEnabled(provider: String) {}
216 | override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
217 | }
218 |
219 | val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
220 | val provider = manager.getProviders(true)
221 |
222 | if (ActivityCompat.checkSelfPermission(
223 | context,
224 | Manifest.permission.ACCESS_FINE_LOCATION
225 | ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
226 | context,
227 | Manifest.permission.ACCESS_COARSE_LOCATION
228 | ) != PackageManager.PERMISSION_GRANTED
229 | ) {
230 | context.requestPermissions(
231 | arrayOf(
232 | Manifest.permission.ACCESS_FINE_LOCATION,
233 | Manifest.permission.ACCESS_COARSE_LOCATION
234 | ), 0
235 | )
236 | return true
237 | }
238 |
239 | if (provider.size > 0) {
240 | }
241 | manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, listener)
242 | return true
243 | }
244 |
245 | @RequiresApi(Build.VERSION_CODES.M)
246 | fun getLastKnownLocation(context: Activity): Location? {
247 |
248 | val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
249 | val provider = manager.getProviders(true)
250 | if (ActivityCompat.checkSelfPermission(
251 | context,
252 | Manifest.permission.ACCESS_FINE_LOCATION
253 | ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
254 | context,
255 | Manifest.permission.ACCESS_COARSE_LOCATION
256 | ) != PackageManager.PERMISSION_GRANTED
257 | ) {
258 | context.requestPermissions(
259 | arrayOf(
260 | Manifest.permission.ACCESS_FINE_LOCATION,
261 | Manifest.permission.ACCESS_COARSE_LOCATION
262 | ), 0
263 | )
264 | return null
265 | }
266 | Log.i("TAG", "getLastKnownLocation: provider.size=${provider.size}")
267 | if (provider.size > 0) {
268 | }
269 | return manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
270 |
271 | }
272 | //@RequiresApi(Build.VERSION_CODES.M)
273 | //fun getLastLocation(context: Activity):Location? {
274 | //
275 | // val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
276 | // val provider = manager.getProviders(true)
277 | // if (ActivityCompat.checkSelfPermission(
278 | // context,
279 | // Manifest.permission.ACCESS_FINE_LOCATION
280 | // ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
281 | // context,
282 | // Manifest.permission.ACCESS_COARSE_LOCATION
283 | // ) != PackageManager.PERMISSION_GRANTED
284 | // ) {
285 | // context.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION),0)
286 | // return null
287 | // }
288 | // Log.i("TAG", "getLastKnownLocation: provider.size=${provider.size}")
289 | // return manager.getLastLocation(LocationManager.GPS_PROVIDER)
290 | //
291 | //}
292 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/utils/PrivacyUtil.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.ActivityManager
5 | import android.app.ActivityManager.RunningAppProcessInfo
6 | import android.content.ContentResolver
7 | import android.hardware.Sensor
8 | import android.hardware.SensorManager
9 | import android.location.Location
10 | import android.location.LocationListener
11 | import android.location.LocationManager
12 | import android.net.DhcpInfo
13 | import android.net.wifi.ScanResult
14 | import android.net.wifi.WifiConfiguration
15 | import android.net.wifi.WifiInfo
16 | import android.net.wifi.WifiManager
17 | import android.provider.Settings
18 | import android.telephony.CellInfo
19 | import android.telephony.TelephonyManager
20 | import android.util.Log
21 | import androidx.annotation.Keep
22 | import com.lanshifu.asm_annotation.AsmMethodReplace
23 | import com.lanshifu.asm_annotation.AsmMethodOpcodes
24 | import com.lanshifu.privacymethodhooker.BuildConfig
25 | import java.util.*
26 | import kotlin.collections.HashMap
27 |
28 | /**
29 | * @author lanxiaobin
30 | * @date 2021/10/9
31 | *
32 | * 1、不要被混淆
33 | *
34 | * 2、Kotlin 的方法必须要使用JvmStatic注解,否则Java调用会报错
35 | *
36 | * java.lang.IncompatibleClassChangeError: The method
37 | * 'java.lang.String com.lanshifu.privacymethodhooker.utils.PrivacyUtil.getString(android.content.ContentResolver, java.lang.String)'
38 | * was expected to be of type static but instead was found to be of type virtual
39 | * (declaration of 'com.lanshifu.privacymethodhooker.MainActivity' appears in /data/app/com.lanshifu.privacymethodhooker-2/base.apk)
40 | */
41 | @Keep
42 | object PrivacyUtil {
43 |
44 | private const val TAG = "PrivacyUtil"
45 |
46 | var isAgreePrivacy: Boolean = false
47 | var isUseCache: Boolean = true
48 |
49 | private var anyCache = HashMap()
50 |
51 | private fun checkAgreePrivacy(name: String): Boolean {
52 | if (!isAgreePrivacy) {
53 | logW("$name: isAgreePrivacy=false")
54 | //没有同意隐私权限,打印堆栈,toast
55 | if (BuildConfig.DEBUG) {
56 | // Toast.makeText(
57 | // ApplicationContext.getContext(),
58 | // "隐私同意前禁止调用$name,现在返回默认值,log过滤PrivacyUtil",
59 | // Toast.LENGTH_LONG
60 | // ).show()
61 | Log.d(TAG, "$name: stack= " + Log.getStackTraceString(Throwable()))
62 | }
63 | return false
64 | }
65 |
66 | return true
67 | }
68 |
69 | private fun getListCache(key: String): List? {
70 | if (!isUseCache){
71 | return null
72 | }
73 | val cache = anyCache[key]
74 | if (cache != null && cache is List<*>) {
75 | try {
76 | return cache as List
77 | } catch (e: Exception) {
78 | Log.w(TAG, "getListCache: key=$key,e=${e.message}")
79 | }
80 | }
81 | logD("getListCache key=$key,return null")
82 | return null
83 | }
84 |
85 | private fun getCache(key: String): T? {
86 | if (!isUseCache){
87 | return null
88 | }
89 | val cache = anyCache[key]
90 | if (cache != null) {
91 | try {
92 | Log.d(TAG, "getCache: key=$key,value=$cache")
93 | return cache as T
94 | } catch (e: Exception) {
95 | Log.w(TAG, "getListCache: key=$key,e=${e.message}")
96 | }
97 | }
98 | logD("getCache key=$key,return null")
99 | return null
100 | }
101 |
102 |
103 | private fun putCache(key: String, value: T): T {
104 | logI("putCache key=$key,value=$value")
105 | value?.let {
106 | anyCache[key] = value
107 | }
108 | return value
109 | }
110 |
111 |
112 | @JvmStatic
113 | @AsmMethodReplace(oriClass = ActivityManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
114 | fun getRunningAppProcesses(manager: ActivityManager): List {
115 | val key = "getRunningAppProcesses"
116 | val cache = getListCache(key)
117 | if (cache != null) {
118 | return cache
119 | }
120 | if (!checkAgreePrivacy(key)) {
121 | return emptyList()
122 | }
123 | val value = manager.runningAppProcesses
124 | return putCache(key, value)
125 | }
126 |
127 | @JvmStatic
128 | @AsmMethodReplace(oriClass = ActivityManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
129 | fun getRecentTasks(
130 | manager: ActivityManager,
131 | maxNum: Int,
132 | flags: Int
133 | ): List? {
134 | val key = "getRecentTasks"
135 | val cache = getListCache(key)
136 | if (cache != null) {
137 | return cache
138 | }
139 | if (!checkAgreePrivacy(key)) {
140 | return emptyList()
141 | }
142 | val value = manager.getRecentTasks(maxNum, flags)
143 | return putCache(key, value)
144 |
145 | }
146 |
147 | @JvmStatic
148 | @AsmMethodReplace(oriClass = ActivityManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
149 | fun getRunningTasks(
150 | manager: ActivityManager,
151 | maxNum: Int
152 | ): List? {
153 | val key = "getRunningTasks"
154 | val cache = getListCache(key)
155 | if (cache != null) {
156 | return cache
157 | }
158 | if (!checkAgreePrivacy(key)) {
159 | return emptyList()
160 | }
161 | val value = manager.getRunningTasks(maxNum)
162 | return putCache(key, value)
163 |
164 | }
165 |
166 | /**
167 | * 读取基站信息,需要开启定位
168 | */
169 | @JvmStatic
170 | @SuppressLint("MissingPermission")
171 | @AsmMethodReplace(oriClass = TelephonyManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
172 | fun getAllCellInfo(manager: TelephonyManager): List? {
173 | val key = "getAllCellInfo"
174 | val cache = getListCache(key)
175 | if (cache != null) {
176 | return cache
177 | }
178 | if (!checkAgreePrivacy(key)) {
179 | return emptyList()
180 | }
181 | val value = manager.getAllCellInfo()
182 | return putCache(key, value)
183 | }
184 |
185 | /**
186 | * 读取基站信息
187 | */
188 | @SuppressLint("HardwareIds")
189 | @JvmStatic
190 | @AsmMethodReplace(oriClass = TelephonyManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
191 | fun getDeviceId(manager: TelephonyManager): String? {
192 | val key = "getDeviceId"
193 | val cache = getCache(key)
194 | if (cache != null) {
195 | return cache
196 | }
197 | if (!checkAgreePrivacy(key)) {
198 | return null
199 | }
200 | //READ_PHONE_STATE 已经整改去掉,返回null
201 | // return manager.deviceId
202 | return putCache(key, null)
203 |
204 | }
205 |
206 | /**
207 | * 读取基站信息
208 | */
209 | @SuppressLint("HardwareIds")
210 | @JvmStatic
211 | @AsmMethodReplace(oriClass = TelephonyManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
212 | fun getImei(manager: TelephonyManager): String? {
213 | val key = "getImei"
214 | val cache = getCache(key)
215 | if (cache != null) {
216 | return cache
217 | }
218 | if (!checkAgreePrivacy(key)) {
219 | return null
220 | }
221 | //READ_PHONE_STATE 已经整改去掉,返回null
222 | return putCache(key, null)
223 | }
224 |
225 | /**
226 | * 读取ICCID
227 | */
228 | @SuppressLint("HardwareIds")
229 | @JvmStatic
230 | @AsmMethodReplace(oriClass = TelephonyManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
231 | fun getSimSerialNumber(manager: TelephonyManager): String? {
232 | val key = "getSimSerialNumber"
233 | val cache = getCache(key)
234 | if (cache != null) {
235 | return cache
236 | }
237 | if (!checkAgreePrivacy(key)) {
238 | return ""
239 | }
240 | //android.Manifest.permission.READ_PHONE_STATE,不允许App读取,拦截调用
241 | return putCache(key, null)
242 | }
243 |
244 | /**
245 | * 读取WIFI的SSID
246 | */
247 | @JvmStatic
248 | @AsmMethodReplace(oriClass = WifiInfo::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
249 | fun getSSID(manager: WifiInfo): String? {
250 | val key = "getSSID"
251 | val cache = getCache(key)
252 | if (cache != null) {
253 | return cache
254 | }
255 |
256 | if (!checkAgreePrivacy(key)) {
257 | return ""
258 | }
259 |
260 | val value = manager.ssid
261 | return putCache(key, value)
262 | }
263 |
264 | /**
265 | * 读取WIFI的SSID
266 | */
267 | @JvmStatic
268 | @AsmMethodReplace(oriClass = WifiInfo::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
269 | fun getBSSID(manager: WifiInfo): String? {
270 | val key = "getBSSID"
271 | val cache = getCache(key)
272 | if (cache != null) {
273 | return cache
274 | }
275 | if (!checkAgreePrivacy(key)) {
276 | return ""
277 | }
278 | val value = manager.bssid
279 | return putCache(key, value)
280 | }
281 |
282 | /**
283 | * 读取WIFI的SSID
284 | */
285 | @SuppressLint("HardwareIds")
286 | @JvmStatic
287 | @AsmMethodReplace(oriClass = WifiInfo::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
288 | fun getMacAddress(manager: WifiInfo): String? {
289 | val key = "getMacAddress"
290 | val cache = getCache(key)
291 | if (cache != null) {
292 | return cache
293 | }
294 | if (!checkAgreePrivacy(key)) {
295 | return ""
296 | }
297 | val value = manager.macAddress
298 | return putCache(key, value)
299 | }
300 |
301 | /**
302 | * 读取AndroidId
303 | */
304 | @JvmStatic
305 | @AsmMethodReplace(oriClass = Settings.System::class, oriAccess = AsmMethodOpcodes.INVOKESTATIC)
306 | fun getString(resolver: ContentResolver, name: String): String? {
307 | //处理AndroidId
308 | if (Settings.Secure.ANDROID_ID == name) {
309 |
310 | val key = "ANDROID_ID"
311 | val cache = getCache(key)
312 | if (cache != null) {
313 | return cache
314 | }
315 | if (!checkAgreePrivacy(key)) {
316 | return ""
317 | }
318 | val value = Settings.System.getString(resolver, name)
319 | return putCache(key, value)
320 | }
321 |
322 | return Settings.System.getString(resolver, name)
323 | }
324 |
325 | /**
326 | * getSensorList
327 | */
328 | @JvmStatic
329 | @AsmMethodReplace(oriClass = SensorManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
330 | fun getSensorList(manager: SensorManager, type: Int): List? {
331 | val key = "getSensorList"
332 | val cache = getListCache(key)
333 | if (cache != null) {
334 | return cache
335 | }
336 | if (!checkAgreePrivacy(key)) {
337 | return mutableListOf()
338 | }
339 | val value = manager.getSensorList(type)
340 | return putCache(key, value)
341 |
342 | }
343 |
344 | /**
345 | * 读取WIFI扫描结果
346 | */
347 | @JvmStatic
348 | @AsmMethodReplace(oriClass = WifiManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
349 | fun getScanResults(manager: WifiManager): List? {
350 | val key = "getScanResults"
351 | val cache = getListCache(key)
352 | if (cache != null) {
353 | return cache
354 | }
355 | if (!checkAgreePrivacy("getScanResults")) {
356 | return mutableListOf()
357 | }
358 | val value = manager.getScanResults()
359 | return putCache(key, value)
360 | }
361 |
362 | /**
363 | * 读取DHCP信息
364 | */
365 | @JvmStatic
366 | @AsmMethodReplace(oriClass = WifiManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
367 | fun getDhcpInfo(manager: WifiManager): DhcpInfo? {
368 | val key = "getDhcpInfo"
369 | val cache = getCache(key)
370 | if (cache != null) {
371 | return cache
372 | }
373 | if (!checkAgreePrivacy(key)) {
374 | return null
375 | }
376 | val value = manager.getDhcpInfo()
377 | return putCache(key, value)
378 |
379 | }
380 |
381 |
382 | /**
383 | * 读取DHCP信息
384 | */
385 | @SuppressLint("MissingPermission")
386 | @JvmStatic
387 | @AsmMethodReplace(oriClass = WifiManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
388 | fun getConfiguredNetworks(manager: WifiManager): List? {
389 | val key = "getConfiguredNetworks"
390 | val cache = getListCache(key)
391 | if (cache != null) {
392 | return cache
393 | }
394 | if (!checkAgreePrivacy(key)) {
395 | return mutableListOf()
396 | }
397 | val value = manager.getConfiguredNetworks()
398 | return putCache(key, value)
399 |
400 | }
401 |
402 |
403 | /**
404 | * 读取位置信息
405 | */
406 | @JvmStatic
407 | @SuppressLint("MissingPermission")
408 | @AsmMethodReplace(oriClass = LocationManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
409 | fun getLastKnownLocation(
410 | manager: LocationManager, provider: String
411 | ): Location? {
412 | if (!checkAgreePrivacy("getLastKnownLocation")) {
413 | return null
414 | }
415 | return manager.getLastKnownLocation(provider)
416 | }
417 |
418 |
419 | /**
420 | * 读取位置信息
421 | */
422 | // @JvmStatic
423 | // @AsmField(oriClass = LocationManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
424 | // fun getLastLocation(
425 | // manager: LocationManager, provider: String
426 | // ): Location? {
427 | // log("getLastKnownLocation: isAgreePrivacy=$isAgreePrivacy")
428 | // if (isAgreePrivacy) {
429 | // return manager.getLastLocation(provider)
430 | // }
431 | // return null
432 | // }
433 |
434 |
435 | /**
436 | * 监视精细行动轨迹
437 | */
438 | @SuppressLint("MissingPermission")
439 | @JvmStatic
440 | @AsmMethodReplace(oriClass = LocationManager::class, oriAccess = AsmMethodOpcodes.INVOKEVIRTUAL)
441 | fun requestLocationUpdates(
442 | manager: LocationManager, provider: String, minTime: Long, minDistance: Float,
443 | listener: LocationListener
444 | ) {
445 | if (!checkAgreePrivacy("requestLocationUpdates")) {
446 | return
447 | }
448 | manager.requestLocationUpdates(provider, minTime, minDistance, listener)
449 |
450 | }
451 |
452 |
453 | private fun logI(log: String) {
454 | Log.i(TAG, log)
455 | }
456 | private fun logD(log: String) {
457 | if (BuildConfig.DEBUG){
458 | Log.d(TAG, log)
459 | }
460 | }
461 |
462 | private fun logW(log: String) {
463 | Log.w(TAG, log)
464 | }
465 |
466 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/utils/ReflectUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker.utils
2 |
3 | import java.lang.reflect.Method
4 |
5 | /**
6 | * @author lanxiaobin
7 | * @date 2021/10/5
8 | */
9 | object ReflectUtils {
10 |
11 | // fun invokeSuperMethod(
12 | // obj: Any,
13 | // name: String,
14 | // types: Array>,
15 | // args: Array?
16 | // ): Any? {
17 | // try {
18 | // val method: Method? = getMethod(obj.javaClass.superclass, name, types)
19 | // if (null != method) {
20 | // method.isAccessible = true
21 | // return method.invoke(obj, args)
22 | // }
23 | // } catch (t: Throwable) {
24 | // t.printStackTrace()
25 | // }
26 | // return null
27 | // }
28 |
29 | private fun getMethod(klass: Class<*>, name: String, types: Array>): Method? {
30 | return try {
31 | klass.getDeclaredMethod(name, *types)
32 | } catch (e: NoSuchMethodException) {
33 | val parent = klass.superclass ?: return null
34 | getMethod(parent, name, types)
35 | }
36 | }
37 |
38 | @JvmStatic
39 | fun invokeMethod(obj: Any, name: String, types: Array>, args: Array): Any? {
40 | try {
41 | val method = getMethod(obj.javaClass, name, types)
42 | if (null != method) {
43 | method.isAccessible = true
44 | return method.invoke(obj, *args)
45 | }
46 | } catch (t: Throwable) {
47 | t.printStackTrace()
48 | }
49 | return null
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lanshifu/privacymethodhooker/utils/Test.java:
--------------------------------------------------------------------------------
1 | package com.lanshifu.privacymethodhooker.utils;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | /**
7 | * @author lanxiaobin
8 | * @date 2021/10/9
9 | */
10 | class Test {
11 |
12 | void test0(Context context) {
13 | ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
14 | //INVOKEVIRTUAL android/app/ActivityManager.getRunningAppProcesses ()Ljava/util/List;
15 | for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
16 |
17 | }
18 | }
19 |
20 | void test() {
21 | ActivityManager activityManager = null;
22 |
23 | //INVOKESTATIC com/lanshifu/privacymethodhooker/utils/PrivacyUtilJava.getRunningAppProcesses (Landroid/app/ActivityManager;)Ljava/util/List;
24 |
25 | for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : PrivacyUtil.getRunningAppProcesses(activityManager)) {
26 |
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
25 |
26 |
31 |
32 |
36 |
37 |
38 |
46 |
47 |
55 |
63 |
64 |
65 |
66 |
70 |
71 |
79 |
80 |
88 |
89 |
90 |
98 |
99 |
100 |
108 |
109 |
110 |
114 |
115 |
123 |
124 |
128 |
136 |
144 |
152 |
160 |
161 |
162 |
166 |
167 |
168 |
176 |
177 |
181 |
182 |
190 |
191 |
199 |
200 |
201 |
205 |
206 |
207 |
215 |
223 |
224 |
225 |
226 |
227 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_first.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_second.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
23 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w1240dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 200dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 48dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PrivacyMethodHooker
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 |
10 | Hello first fragment
11 | Hello second fragment. Arg: %1$s
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/asm_annotation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/asm_annotation/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 33
8 | buildToolsVersion "30.0.3"
9 |
10 | defaultConfig {
11 | minSdkVersion 21
12 | targetSdkVersion 33
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_11
27 | targetCompatibility JavaVersion.VERSION_11
28 | }
29 |
30 | kotlinOptions {
31 | jvmTarget = '11'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation 'org.ow2.asm:asm:9.3'
37 | }
38 |
--------------------------------------------------------------------------------
/asm_annotation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/asm_annotation/consumer-rules.pro
--------------------------------------------------------------------------------
/asm_annotation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/asm_annotation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/asm_annotation/src/main/java/com/lanshifu/asm_annotation/AsmMethodOpcodes.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.asm_annotation
2 |
3 | import org.objectweb.asm.Opcodes
4 |
5 | /**
6 | * @author lanxiaobin
7 | * @date 2021/10/10
8 | *
9 | * Opcodes 转换,业务可以不依赖 'org.ow2.asm:asm:7.1'
10 | */
11 | object AsmMethodOpcodes {
12 |
13 | const val INVOKESTATIC = Opcodes.INVOKESTATIC
14 | const val INVOKEVIRTUAL = Opcodes.INVOKEVIRTUAL
15 | const val INVOKESPECIAL = Opcodes.INVOKESPECIAL
16 | const val INVOKEDYNAMIC = Opcodes.INVOKEDYNAMIC
17 | const val INVOKEINTERFACE = Opcodes.INVOKEINTERFACE
18 | }
--------------------------------------------------------------------------------
/asm_annotation/src/main/java/com/lanshifu/asm_annotation/AsmMethodReplace.java:
--------------------------------------------------------------------------------
1 | package com.lanshifu.asm_annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 |
9 | /**
10 | * @author lanxiaobin
11 | * @date 2021/9/30.
12 | */
13 | @Target(ElementType.METHOD)
14 | @Retention(RetentionPolicy.CLASS)
15 | public @interface AsmMethodReplace {
16 | Class oriClass();
17 |
18 | String oriMethod() default "";
19 |
20 | int oriAccess() default AsmMethodOpcodes.INVOKESTATIC;
21 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | apply from: "config.gradle"
3 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
4 | buildscript {
5 | ext.agp_version = '7.2.2'
6 | ext.kotlin_version = '1.7.10'
7 | ext.booster_version = '4.0.0'
8 | repositories {
9 | google()
10 | mavenCentral()
11 | }
12 | dependencies {
13 | classpath "com.android.tools.build:gradle:${agp_version}"
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}"
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | maven { url 'local_repo' }
21 | google()
22 | mavenCentral()
23 | maven { url "https://jitpack.io" }
24 | maven { url 'https://oss.sonatype.org/content/repositories/public/' }
25 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | apply from: "../config.gradle"
3 | ext {
4 | kotlin_version = '1.7.10'
5 | agp_version = '7.2.2'
6 | booster_version = '4.0.0'
7 | }
8 | repositories {
9 | mavenLocal()
10 | mavenCentral()
11 | google()
12 | maven { url 'https://oss.sonatype.org/content/repositories/public/' }
13 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
14 | }
15 | dependencies {
16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
17 | classpath "com.android.tools.build:gradle:$agp_version"
18 | }
19 | }
20 |
21 | apply plugin: 'groovy'
22 | apply plugin: 'kotlin'
23 | apply plugin: 'kotlin-kapt'
24 | apply plugin: 'kotlin-android-extensions'
25 |
26 | repositories {
27 | mavenLocal()
28 | mavenCentral()
29 | google()
30 | maven { url 'https://oss.sonatype.org/content/repositories/public/' }
31 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
32 | }
33 |
34 | sourceSets {
35 | main {
36 | java {
37 | srcDirs += ['src/main/kotlin', 'src/main/java']
38 | }
39 | kotlin {
40 | srcDirs += ['src/main/kotlin', 'src/main/java']
41 | }
42 | }
43 | test {
44 | java {
45 | srcDirs += ['src/main/kotlin', 'src/main/java']
46 | }
47 | kotlin {
48 | srcDirs += ['src/main/kotlin', 'src/main/java']
49 | }
50 | }
51 | }
52 |
53 | compileKotlin {
54 | kotlinOptions.jvmTarget = JavaVersion.VERSION_11
55 | }
56 |
57 | compileTestKotlin {
58 | kotlinOptions.jvmTarget = JavaVersion.VERSION_11
59 | }
60 |
61 | java {
62 | sourceCompatibility = JavaVersion.VERSION_11
63 | targetCompatibility = JavaVersion.VERSION_11
64 | }
65 |
66 | dependencies {
67 | compileOnly localGroovy()
68 | compileOnly gradleApi()
69 | //ASM相关依赖
70 | implementation 'org.ow2.asm:asm:9.3'
71 | implementation 'org.ow2.asm:asm-commons:9.3'
72 | implementation 'com.android.tools.build:gradle:7.2.2'
73 |
74 | kapt "com.google.auto.service:auto-service:1.0.1"
75 | api "com.didiglobal.booster:booster-api:$booster_version"
76 | api "com.didiglobal.booster:booster-transform-asm:$booster_version"
77 | }
78 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/DelegateTransformInvocation.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin
2 |
3 | import com.android.build.api.transform.*
4 | import com.android.build.api.transform.Status.*
5 | import com.didiglobal.booster.gradle.*
6 | import com.didiglobal.booster.kotlinx.NCPU
7 | import com.didiglobal.booster.transform.AbstractKlassPool
8 | import com.didiglobal.booster.transform.ArtifactManager
9 | import com.didiglobal.booster.transform.TransformContext
10 | import com.didiglobal.booster.transform.artifacts
11 | import com.didiglobal.booster.transform.util.transform
12 | import com.lanshifu.plugin.extension.doTransform
13 | import com.lanshifu.plugin.extension.println
14 | import com.lanshifu.plugin.transform.BaseTransform
15 | import java.io.File
16 | import java.net.URI
17 | import java.util.concurrent.CopyOnWriteArrayList
18 | import java.util.concurrent.Executors
19 | import java.util.concurrent.TimeUnit
20 |
21 | /**
22 | * Represents a delegate of TransformInvocation
23 | *
24 | * @author johnsonlee
25 | */
26 | internal class DelegateTransformInvocation(
27 | private val delegate: TransformInvocation,
28 | internal val transform: BaseTransform
29 | ) : TransformInvocation by delegate, TransformContext, ArtifactManager {
30 |
31 | val executor = Executors.newFixedThreadPool(NCPU)
32 |
33 | private val project = transform.project
34 |
35 | private val outputs = CopyOnWriteArrayList()
36 |
37 | override val name: String = delegate.context.variantName
38 |
39 | override val projectDir: File = project.projectDir
40 |
41 | override val buildDir: File = project.buildDir
42 |
43 | override val temporaryDir: File = delegate.context.temporaryDir
44 |
45 | override val reportsDir: File = File(buildDir, "reports").also { it.mkdirs() }
46 |
47 | override val bootClasspath = delegate.bootClasspath
48 |
49 | override val compileClasspath = delegate.compileClasspath
50 |
51 | override val runtimeClasspath = delegate.runtimeClasspath
52 |
53 | override val artifacts = this
54 |
55 | override val dependencies: Collection by lazy {
56 | ResolvedArtifactResults(variant).map {
57 | it.id.displayName
58 | }
59 | }
60 |
61 | override val klassPool: AbstractKlassPool =
62 | object : AbstractKlassPool(compileClasspath, transform.bootKlassPool) {}
63 |
64 | override val applicationId = delegate.applicationId
65 |
66 | override val originalApplicationId = delegate.originalApplicationId
67 |
68 | override val isDebuggable = variant.buildType.isDebuggable
69 |
70 | override val isDataBindingEnabled = delegate.isDataBindingEnabled
71 |
72 | override fun hasProperty(name: String) = project.hasProperty(name)
73 |
74 | override fun getProperty(name: String, default: T): T = project.getProperty(name, default)
75 |
76 | override fun get(type: String) = variant.artifacts.get(type)
77 |
78 | internal fun doFullTransform() = doTransform(true)
79 |
80 | internal fun doIncrementalTransform() = doTransform(false)
81 |
82 | private fun onPreTransform() {
83 | transform.transformers.forEach {
84 | it.onPreTransform(this)
85 | }
86 | }
87 |
88 | private fun onPostTransform() {
89 | transform.transformers.forEach {
90 | it.onPostTransform(this)
91 | }
92 | }
93 |
94 | private fun doTransform(full:Boolean) {
95 | this.outputs.clear()
96 | this.onPreTransform()
97 |
98 | try {
99 | if (full){
100 | transformFully()
101 | } else {
102 | transformIncrementally()
103 | }
104 | } finally {
105 | executor.shutdown()
106 | executor.awaitTermination(1, TimeUnit.HOURS)
107 | }
108 |
109 | this.onPostTransform()
110 |
111 | if (transform.verifyEnabled) {
112 | this.doVerify()
113 | }
114 | }
115 |
116 | private fun transformFully() = this.inputs.map {
117 | it.jarInputs + it.directoryInputs
118 | }.flatten().map { input ->
119 | executor.submit {
120 | val format = if (input is DirectoryInput) Format.DIRECTORY else Format.JAR
121 | outputProvider?.let { provider ->
122 | project.logger.info("Transforming ${input.file}")
123 | input.transform(
124 | provider.getContentLocation(
125 | input.name,
126 | input.contentTypes,
127 | input.scopes,
128 | format
129 | )
130 | )
131 | }
132 | }.get()
133 | }
134 |
135 | private fun transformIncrementally() = this.inputs.map { input ->
136 | input.jarInputs.filter { it.status != NOTCHANGED }.map { jarInput ->
137 | executor.submit {
138 | doIncrementalTransform(jarInput)
139 | }.get()
140 | } + input.directoryInputs.filter { it.changedFiles.isNotEmpty() }.map { dirInput ->
141 | val base = dirInput.file.toURI()
142 | executor.submit {
143 | doIncrementalTransform(dirInput, base)
144 | }.get()
145 | }
146 | }.flatten()
147 |
148 | @Suppress("NON_EXHAUSTIVE_WHEN")
149 | private fun doIncrementalTransform(jarInput: JarInput) {
150 | when (jarInput.status) {
151 | REMOVED -> jarInput.file.delete()
152 | CHANGED, ADDED -> {
153 | project.logger.info("Transforming ${jarInput.file}")
154 | outputProvider?.let { provider ->
155 | jarInput.transform(
156 | provider.getContentLocation(
157 | jarInput.name,
158 | jarInput.contentTypes,
159 | jarInput.scopes,
160 | Format.JAR
161 | )
162 | )
163 | }
164 | }
165 | else -> {
166 | project.logger.info("${jarInput.file} not changed")
167 | }
168 | }
169 | }
170 |
171 | @Suppress("NON_EXHAUSTIVE_WHEN")
172 | private fun doIncrementalTransform(dirInput: DirectoryInput, base: URI) {
173 | dirInput.changedFiles.forEach { (file, status) ->
174 | when (status) {
175 | REMOVED -> {
176 | project.logger.info("Deleting $file")
177 | outputProvider?.let { provider ->
178 | provider.getContentLocation(
179 | dirInput.name,
180 | dirInput.contentTypes,
181 | dirInput.scopes,
182 | Format.DIRECTORY
183 | ).parentFile.listFiles()?.asSequence()
184 | ?.filter { it.isDirectory }
185 | ?.map { File(it, dirInput.file.toURI().relativize(file.toURI()).path) }
186 | ?.filter { it.exists() }
187 | ?.forEach { it.delete() }
188 | }
189 | file.delete()
190 | }
191 | ADDED, CHANGED -> {
192 | project.logger.info("Transforming $file")
193 | outputProvider?.let { provider ->
194 | val root = provider.getContentLocation(
195 | dirInput.name,
196 | dirInput.contentTypes,
197 | dirInput.scopes,
198 | Format.DIRECTORY
199 | )
200 | val output = File(root, base.relativize(file.toURI()).path)
201 | outputs += output
202 | file.transform(output) { bytecode ->
203 | bytecode.transform()
204 | }
205 | }
206 | }
207 | else -> {
208 | project.logger.info("$file not changed")
209 | }
210 | }
211 | }
212 | }
213 |
214 | private fun doVerify() {
215 | // TODO unnecessary since Android 5.0
216 | // outputs.sortedBy(File::nameWithoutExtension).forEach { output ->
217 | // val out = temporaryDir.file(output.name)
218 | // val rc = out.dex(
219 | // output,
220 | // variant.extension.defaultConfig.targetSdkVersion?.apiLevel
221 | // ?: DexFormat.API_NO_EXTENDED_OPCODES
222 | // )
223 | // println("${if (rc != 0) red("✗") else green("✓")} $output")
224 | // out.deleteRecursively()
225 | // }
226 | }
227 |
228 | private fun QualifiedContent.transform(output: File) {
229 | outputs += output
230 | try {
231 | this.file.doTransform(output) { bytecode ->
232 | bytecode.transform()
233 | }
234 | } catch (e: Exception) {
235 | "e===>${e.message}".println()
236 | e.printStackTrace()
237 | }
238 |
239 | }
240 |
241 | private fun ByteArray.transform(): ByteArray {
242 | return transform.transformers.fold(this) { bytes, transformer ->
243 | transformer.transform(this@DelegateTransformInvocation, bytes)
244 | }
245 | }
246 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/Plugin.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin
2 |
3 | import com.android.build.gradle.AppExtension
4 | import com.didiglobal.booster.gradle.getAndroid
5 | import com.lanshifu.plugin.transform.CommonTransform
6 | import com.lanshifu.plugin.transform.PrivacyMethodHookTransform
7 | import org.gradle.api.Plugin
8 | import org.gradle.api.Project
9 |
10 | /**
11 | * @author lanxiaobin
12 | * @date 2021/11/13
13 | */
14 | class Plugin : Plugin {
15 | override fun apply(project: Project) {
16 |
17 | when {
18 | project.plugins.hasPlugin("com.android.application") ||
19 | project.plugins.hasPlugin("com.android.dynamic-feature") -> {
20 |
21 | project.getAndroid().let { androidExt ->
22 |
23 | androidExt.registerTransform(
24 | CommonTransform(project)
25 | )
26 | androidExt.registerTransform(
27 | PrivacyMethodHookTransform(project)
28 | )
29 |
30 |
31 | }
32 |
33 | }
34 | }
35 |
36 |
37 | }
38 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/asmtransformer/BaseAsmTransformer.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.asmtransformer
2 |
3 | import com.didiglobal.booster.annotations.Priority
4 | import com.didiglobal.booster.transform.TransformContext
5 | import com.didiglobal.booster.transform.Transformer
6 | import com.didiglobal.booster.transform.asm.ClassTransformer
7 | import org.objectweb.asm.ClassReader
8 | import org.objectweb.asm.ClassWriter
9 | import org.objectweb.asm.tree.ClassNode
10 | import java.lang.management.ManagementFactory
11 | import java.lang.management.ThreadMXBean
12 | import java.util.*
13 |
14 | /**
15 | * BaseAsmTransformer,通过构造传参 transformers,
16 | * 可以在一个Transformer中处理多个 ClassTransformer
17 | */
18 | open class BaseAsmTransformer : Transformer {
19 | private val threadMxBean = ManagementFactory.getThreadMXBean()
20 |
21 | private val durations = mutableMapOf()
22 |
23 | private val classLoader: ClassLoader
24 |
25 | internal val transformers: Iterable
26 |
27 | constructor() : this(Thread.currentThread().contextClassLoader)
28 |
29 | constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : this(
30 | ServiceLoader.load(ClassTransformer::class.java, classLoader).sortedBy {
31 | it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0
32 | }, classLoader
33 | )
34 |
35 | constructor(
36 | transformers: Iterable,
37 | classLoader: ClassLoader = Thread.currentThread().contextClassLoader
38 | ) {
39 | this.classLoader = classLoader
40 | this.transformers = transformers
41 | }
42 |
43 |
44 | override fun onPreTransform(context: TransformContext) {
45 | this.transformers.forEach { transformer ->
46 | this.threadMxBean.sumCpuTime(transformer) {
47 | transformer.onPreTransform(context)
48 | }
49 | }
50 | }
51 |
52 | override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {
53 | return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer ->
54 | this.transformers.fold(ClassNode().also { klass ->
55 | ClassReader(bytecode).accept(klass, 0)
56 | }) { klass, transformer ->
57 | this.threadMxBean.sumCpuTime(transformer) {
58 | transformer.transform(context, klass)
59 | }
60 | }.accept(writer)
61 | }.toByteArray()
62 | }
63 |
64 | override fun onPostTransform(context: TransformContext) {
65 | this.transformers.forEach { transformer ->
66 | this.threadMxBean.sumCpuTime(transformer) {
67 | transformer.onPostTransform(context)
68 | }
69 | }
70 |
71 | val w1 = this.durations.keys.map {
72 | it.javaClass.name.length
73 | }.maxOrNull() ?: 20
74 | this.durations.forEach { (transformer, ns) ->
75 | println("${transformer.javaClass.name.padEnd(w1 + 1)}: ${ns / 1000000} ms")
76 | }
77 | }
78 |
79 | private fun ThreadMXBean.sumCpuTime(transformer: ClassTransformer, action: () -> R): R {
80 | val ct0 = this.currentThreadCpuTime
81 | val result = action()
82 | val ct1 = this.currentThreadCpuTime
83 | durations[transformer] = durations.getOrDefault(transformer, 0) + (ct1 - ct0)
84 | return result
85 | }
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/classtransformer/AbsClassTransformer.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.classtransformer
2 |
3 | import com.didiglobal.booster.transform.TransformContext
4 | import com.didiglobal.booster.transform.asm.ClassTransformer
5 | import com.didiglobal.booster.transform.asm.className
6 | import com.lanshifu.plugin.extension.isRelease
7 | import org.objectweb.asm.tree.ClassNode
8 |
9 | /**
10 | * 提供拦截方法,可以统一过滤不需要hook的类
11 | */
12 | open class AbsClassTransformer : ClassTransformer {
13 |
14 |
15 | fun onCommInterceptor(context: TransformContext, klass: ClassNode): Boolean {
16 | // "===onCommInterceptor--->$this====${klass.className}===".println()
17 | if (context.isRelease()) {
18 | return true
19 | }
20 | //
21 | // if (!DoKitExtUtil.dokitPluginSwitchOpen()) {
22 | // return true
23 | // }
24 | //过滤kotlin module-info
25 | if (klass.className == "module-info") {
26 | return true
27 | }
28 |
29 | return false
30 | }
31 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/classtransformer/AnnotationParserClassTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.classtransformer
2 |
3 | import com.didiglobal.booster.kotlinx.file
4 | import com.didiglobal.booster.kotlinx.touch
5 | import com.didiglobal.booster.transform.TransformContext
6 | import com.lanshifu.plugin.privacymethod.AsmItem
7 | import org.objectweb.asm.tree.ClassNode
8 | import java.io.PrintWriter
9 |
10 | /**
11 | * @author lanxiaobin
12 | * @date 2021/11/11
13 | */
14 | class AnnotationParserClassTransform : AbsClassTransformer() {
15 |
16 | private lateinit var logger: PrintWriter
17 |
18 | companion object{
19 | const val AsmFieldDesc = "Lcom/lanshifu/asm_annotation/AsmMethodReplace;"
20 | var asmConfigs = mutableListOf()
21 | var asmConfigsMap = HashMap()
22 | }
23 |
24 | override fun onPreTransform(context: TransformContext) {
25 | super.onPreTransform(context)
26 | this.logger = context.reportsDir.file("AnnotationParserClassTransform").file(context.name).file("report.txt").touch().printWriter()
27 | logger.println("--start-- ${System.currentTimeMillis()}")
28 | }
29 |
30 | override fun onPostTransform(context: TransformContext) {
31 | logger.println("\n--end-- ${System.currentTimeMillis()}")
32 | this.logger.close()
33 | }
34 |
35 | override fun transform(context: TransformContext, klass: ClassNode) = klass.also {
36 | if (onCommInterceptor(context, klass)) {
37 | return klass
38 | }
39 |
40 | klass.methods.forEach { method->
41 | method.invisibleAnnotations?.forEach { node ->
42 | if (node.desc == AsmFieldDesc) {
43 | val asmItem = AsmItem(klass.name, method, node)
44 | if (!asmConfigs.contains(asmItem)) {
45 | logger.print("\nadd AsmItem:$asmItem")
46 | asmConfigs.add(asmItem)
47 | asmConfigsMap.put(klass.name,klass.name)
48 | }
49 | }
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/classtransformer/PrivacyMethodReplaceTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.classtransformer
2 |
3 | import com.didiglobal.booster.kotlinx.file
4 | import com.didiglobal.booster.kotlinx.touch
5 | import com.didiglobal.booster.transform.TransformContext
6 | import org.objectweb.asm.tree.ClassNode
7 | import org.objectweb.asm.tree.MethodInsnNode
8 | import java.io.PrintWriter
9 |
10 |
11 | /**
12 | * @author lanxiaobin
13 | * @date 2021/11/11
14 | */
15 | class PrivacyMethodReplaceTransform : AbsClassTransformer() {
16 | private lateinit var logger: PrintWriter
17 | private val asmItems = AnnotationParserClassTransform.asmConfigs
18 | private var asmItemsClassMap: HashMap = HashMap()
19 |
20 | override fun onPreTransform(context: TransformContext) {
21 | super.onPreTransform(context)
22 | this.logger = context.reportsDir.file("PrivacyMethodReplaceTransform").file(context.name)
23 | .file("report.txt").touch().printWriter()
24 | logger.println("--start-- ${System.currentTimeMillis()}")
25 |
26 | AnnotationParserClassTransform.asmConfigs.forEach {
27 | asmItemsClassMap[it.targetClass] = it.targetClass
28 | }
29 | logger.print("\nasmItemsMap size=${asmItemsClassMap.size},asmItems.size=${asmItems.size}\n\n")
30 | logger.print("asmItemsClassMap=${asmItemsClassMap} \n\n")
31 | logger.print("asmItems=${asmItems} \n\n")
32 | }
33 |
34 | override fun onPostTransform(context: TransformContext) {
35 | logger.println("\n --end-- ${System.currentTimeMillis()}")
36 | this.logger.close()
37 | }
38 |
39 | override fun transform(context: TransformContext, klass: ClassNode) = klass.also {
40 |
41 | if (onCommInterceptor(context, klass)) {
42 | return klass
43 | }
44 |
45 | if (AnnotationParserClassTransform.asmConfigsMap.contains(klass.name)) {
46 | logger.print("\nPrivacyMethodReplaceAsmHelper modifyClass ignore,classNode.name=${klass.name}\n")
47 | return@also
48 | }
49 |
50 | klass.methods.forEach { method ->
51 | method.instructions?.iterator()?.forEach { insnNode ->
52 |
53 | if (insnNode is MethodInsnNode) {
54 |
55 | asmItems.forEach { asmItem ->
56 | //INVOKEVIRTUAL android/app/ActivityManager.getRunningAppProcesses ()Ljava/util/List; ->
57 | //INVOKESTATIC com/lanshifu/asm_plugin_library/privacy/PrivacyUtil.getRunningAppProcesses (Landroid/app/ActivityManager;)Ljava/util/List;
58 | if (asmItem.oriDesc == insnNode.desc && asmItem.oriMethod == insnNode.name
59 | && insnNode.opcode == asmItem.oriAccess &&
60 | (insnNode.owner == asmItem.oriClass || asmItem.oriClass == "java/lang/Object")
61 | ) {
62 |
63 | logger.print(
64 | "\nhook:\n" +
65 | "opcode=${insnNode.opcode},owner=${insnNode.owner},desc=${insnNode.desc},name=${klass.name}#${insnNode.name} ->\n" +
66 | "opcode=${asmItem.targetAccess},owner=${asmItem.targetClass},desc=${asmItem.targetDesc},name=${asmItem.targetMethod}\n"
67 | )
68 |
69 | insnNode.opcode = asmItem.targetAccess
70 | insnNode.desc = asmItem.targetDesc
71 | insnNode.owner = asmItem.targetClass
72 | insnNode.name = asmItem.targetMethod
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
80 |
81 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/extension/CommonExt.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.extension
2 |
3 | import com.android.build.gradle.api.BaseVariant
4 | import com.didiglobal.booster.kotlinx.NCPU
5 | import com.didiglobal.booster.kotlinx.redirect
6 | import com.didiglobal.booster.kotlinx.search
7 | import com.didiglobal.booster.kotlinx.touch
8 | import com.didiglobal.booster.transform.TransformContext
9 | import com.didiglobal.booster.transform.util.transform
10 | import org.apache.commons.compress.archivers.jar.JarArchiveEntry
11 | import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator
12 | import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
13 | import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
14 | import org.apache.commons.compress.parallel.InputStreamSupplier
15 | import org.objectweb.asm.Opcodes.*
16 | import org.objectweb.asm.tree.*
17 | import java.io.File
18 | import java.io.IOException
19 | import java.io.OutputStream
20 | import java.util.concurrent.*
21 | import java.util.jar.JarFile
22 | import java.util.zip.ZipEntry
23 | import java.util.zip.ZipFile
24 |
25 | /**
26 | * ================================================
27 | * 作 者:jint(金台)
28 | * 版 本:1.0
29 | * 创建日期:2020/5/19-18:00
30 | * 描 述:dokit 对象扩展
31 | * 修订历史:
32 | * ================================================
33 | */
34 |
35 | fun MethodNode.isGetSetMethod(): Boolean {
36 | var ignoreCount = 0
37 | val iterator = instructions.iterator()
38 | while (iterator.hasNext()) {
39 | val insnNode = iterator.next()
40 | val opcode = insnNode.opcode
41 | if (-1 == opcode) {
42 | continue
43 | }
44 | if (opcode != GETFIELD && opcode != GETSTATIC && opcode != H_GETFIELD && opcode != H_GETSTATIC && opcode != RETURN && opcode != ARETURN && opcode != DRETURN && opcode != FRETURN && opcode != LRETURN && opcode != IRETURN && opcode != PUTFIELD && opcode != PUTSTATIC && opcode != H_PUTFIELD && opcode != H_PUTSTATIC && opcode > SALOAD) {
45 | if (name.equals("") && opcode == INVOKESPECIAL) {
46 | ignoreCount++
47 | if (ignoreCount > 1) {
48 | return false
49 | }
50 | continue
51 | }
52 | return false
53 | }
54 | }
55 | return true
56 | }
57 |
58 | fun MethodNode.isSingleMethod(): Boolean {
59 | val iterator = instructions.iterator()
60 | while (iterator.hasNext()) {
61 | val insnNode = iterator.next()
62 | val opcode = insnNode.opcode
63 | if (-1 == opcode) {
64 | continue
65 | } else if (INVOKEVIRTUAL <= opcode && opcode <= INVOKEDYNAMIC) {
66 | return false
67 | }
68 | }
69 | return true
70 | }
71 |
72 | fun MethodNode.isEmptyMethod(): Boolean {
73 | val iterator = instructions.iterator()
74 | while (iterator.hasNext()) {
75 | val insnNode = iterator.next()
76 | val opcode = insnNode.opcode
77 | return if (-1 == opcode) {
78 | continue
79 | } else {
80 | false
81 | }
82 | }
83 | return true
84 | }
85 |
86 | fun MethodNode.isMainMethod(className: String): Boolean {
87 | if (this.name == "main" && this.desc == "([Ljava/lang/String;)V") {
88 | // "====isMainMethod====$className ${this.name} ${this.desc} ${this.access}".println()
89 | return true
90 | }
91 |
92 | return false
93 | }
94 |
95 |
96 | fun InsnList.getMethodExitInsnNodes(): Sequence? {
97 | return this.iterator()?.asSequence()?.filterIsInstance(InsnNode::class.java)?.filter {
98 | it.opcode == RETURN ||
99 | it.opcode == IRETURN ||
100 | it.opcode == FRETURN ||
101 | it.opcode == ARETURN ||
102 | it.opcode == LRETURN ||
103 | it.opcode == DRETURN ||
104 | it.opcode == ATHROW
105 | }
106 | }
107 |
108 | fun BaseVariant.isRelease(): Boolean {
109 | if (this.name.contains("release") || this.name.contains("Release")) {
110 | return true
111 | }
112 | return false
113 | }
114 |
115 |
116 | fun TransformContext.isRelease(): Boolean {
117 | if (this.name.contains("release") || this.name.contains("Release")) {
118 | return true
119 | }
120 | return false
121 | }
122 |
123 |
124 | fun String.println() {
125 | println("[lizhi plugin]===>$this")
126 | }
127 |
128 | fun File.lastPath(): String {
129 | return this.path.split("/").last()
130 | }
131 |
132 | val MethodInsnNode.ownerClassName: String
133 | get() = owner.replace('/', '.')
134 |
135 |
136 | val ClassNode.formatSuperName: String
137 | get() = superName.replace('/', '.')
138 |
139 | /**
140 | * Transform this file or directory to the output by the specified transformer
141 | *
142 | * @param output The output location
143 | * @param transformer The byte data transformer
144 | */
145 | fun File.doTransform(output: File, transformer: (ByteArray) -> ByteArray = { it -> it }) {
146 | when {
147 | isDirectory -> this.toURI().let { base ->
148 | this.search().parallelStream().forEach {
149 | it.transform(File(output, base.relativize(it.toURI()).path), transformer)
150 | }
151 | }
152 | isFile -> when (extension.toLowerCase()) {
153 | "jar" -> JarFile(this).use {
154 | it.doTransform(output, { JarArchiveEntry(it) }, transformer)
155 | }
156 | "class" -> this.inputStream().use {
157 | it.transform(transformer).redirect(output)
158 | }
159 | else -> this.copyTo(output, true)
160 | }
161 | else -> throw IOException("Unexpected file: ${this.canonicalPath}")
162 | }
163 | }
164 |
165 | fun ZipFile.doTransform(
166 | output: File,
167 | entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,
168 | transformer: (ByteArray) -> ByteArray = { it -> it }
169 | ) = output.touch().outputStream().buffered().use {
170 | this.doTransform(it, entryFactory, transformer)
171 | }
172 |
173 |
174 | fun ZipFile.doTransform(
175 | output: OutputStream,
176 | entryFactory: (ZipEntry) -> ZipArchiveEntry = ::ZipArchiveEntry,
177 | transformer: (ByteArray) -> ByteArray = { it -> it }
178 | ) {
179 | val entries = mutableSetOf()
180 | val creator = ParallelScatterZipCreator(
181 | ThreadPoolExecutor(
182 | NCPU,
183 | NCPU,
184 | 0L,
185 | TimeUnit.MILLISECONDS,
186 | LinkedBlockingQueue(),
187 | Executors.defaultThreadFactory(),
188 | RejectedExecutionHandler { runnable, _ ->
189 | runnable.run()
190 | })
191 | )
192 | //将jar包里的文件序列化输出
193 | entries().asSequence().forEach { entry ->
194 | if (!entries.contains(entry.name)) {
195 | val zae = entryFactory(entry)
196 |
197 | val stream = InputStreamSupplier {
198 | when (entry.name.substringAfterLast('.', "")) {
199 | "class" -> getInputStream(entry).use { src ->
200 | try {
201 | src.transform(transformer).inputStream()
202 | } catch (e: Throwable) {
203 | System.err.println("Broken class: ${this.name}!/${entry.name}")
204 | getInputStream(entry)
205 | }
206 | }
207 | else -> getInputStream(entry)
208 | }
209 | }
210 |
211 | creator.addArchiveEntry(zae, stream)
212 | entries.add(entry.name)
213 | } else {
214 | System.err.println("Duplicated jar entry: ${this.name}!/${entry.name}")
215 | }
216 | }
217 | val zip = ZipArchiveOutputStream(output)
218 | zip.use { zipStream ->
219 | try {
220 | creator.writeTo(zipStream)
221 | zipStream.close()
222 | } catch (e: Exception) {
223 | zipStream.close()
224 | // e.printStackTrace()
225 | // "e===>${e.message}".println()
226 | System.err.println("Duplicated jar entry: ${this.name}!")
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/privacymethod/AsmItem.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.privacymethod
2 |
3 | import groovyjarjarasm.asm.Opcodes
4 |
5 | /**
6 | * @author lanxiaobin
7 | * @date 2021/9/30
8 | */
9 | class AsmItem(
10 | targetClass: String,
11 | methodNode: org.objectweb.asm.tree.MethodNode,
12 | node: org.objectweb.asm.tree.AnnotationNode
13 | ) {
14 | var oriClass: String? = null
15 | var oriMethod: String? = null
16 | var oriDesc: String? = null
17 | var oriAccess = Opcodes.INVOKESTATIC
18 |
19 | var targetClass: String = ""
20 | var targetMethod: String = ""
21 | var targetDesc: String = ""
22 | var targetAccess = Opcodes.INVOKESTATIC
23 |
24 | init {
25 | this.targetClass = targetClass
26 | this.targetMethod = methodNode.name
27 | this.targetDesc = methodNode.desc
28 | var sourceName: String = ""
29 | //注解是key value形式,
30 | for (i in 0 until node.values.size / 2) {
31 | val key = node.values[i * 2]
32 | val value = node.values[i * 2 + 1]
33 | if (key == "oriClass") {
34 | sourceName = value.toString()
35 | oriClass = sourceName.substring(1, sourceName.length - 1)
36 | } else if (key == "oriAccess") {
37 | oriAccess = value as Int
38 | } else if (key === "oriMethod") {
39 | oriMethod = value as String?
40 | }
41 | }
42 | if (oriMethod == null) {
43 | oriMethod = targetMethod
44 | }
45 | //静态方法,oriDesc 跟 targetDesc 一样
46 | if (oriAccess == Opcodes.INVOKESTATIC) {
47 | oriDesc = targetDesc
48 | } else {
49 | //非静态方法,约定第一个参数是实例类名,oriDesc 比 targetDesc 少一个参数,处理一下
50 | var param = targetDesc.split(")")[0] + ")" //(Landroid/telephony/TelephonyManager;)
51 | val returnValue = targetDesc.split(")")[1] //Ljava/lang/String;
52 | if (param.indexOf(sourceName) == 1) {
53 | param = "(" + param.substring(param.indexOf(sourceName) + sourceName.length)
54 | }
55 | oriDesc = param + returnValue
56 |
57 | //处理之后
58 | //targetDesc=(Landroid/telephony/TelephonyManager;)Ljava/lang/String;
59 | //oriDesc= Ljava/lang/String;
60 | }
61 | }
62 |
63 | override fun equals(other: Any?): Boolean {
64 | if (other is AsmItem) {
65 | return (other.oriAccess == oriAccess &&
66 | other.oriClass == oriClass &&
67 | other.oriDesc == oriDesc &&
68 | other.oriMethod == oriMethod
69 | )
70 | }
71 |
72 | return super.equals(other)
73 | }
74 |
75 | override fun toString(): String {
76 | return "AsmItem(oriClass=$oriClass, oriMethod=$oriMethod, oriDesc=$oriDesc, oriAccess=$oriAccess, targetClass='$targetClass', targetMethod='$targetMethod', targetDesc='$targetDesc', targetAccess=$targetAccess)"
77 | }
78 |
79 |
80 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/transform/BaseTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.transform
2 |
3 | import com.android.build.api.transform.QualifiedContent
4 | import com.android.build.api.transform.Transform
5 | import com.android.build.api.transform.TransformInvocation
6 | import com.android.build.gradle.BaseExtension
7 | import com.android.build.gradle.internal.pipeline.TransformManager
8 | import com.didiglobal.booster.gradle.*
9 | import com.didiglobal.booster.transform.AbstractKlassPool
10 | import com.didiglobal.booster.transform.Transformer
11 | import com.lanshifu.plugin.DelegateTransformInvocation
12 | import org.gradle.api.Project
13 |
14 | /**
15 | * Represents the transform base
16 | * DoKitCommTransform 作用于 CommTransformer、BigImgTransformer、UrlConnectionTransformer、GlobalSlowMethodTransformer
17 | * @author johnsonlee
18 | */
19 | open class BaseTransform protected constructor(val project: Project) : Transform() {
20 |
21 | /*transformers
22 | * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode
23 | * booster 的默认出炉逻辑 DoKit已重写自处理
24 | */
25 | internal open val transformers = listOf()
26 |
27 | internal val verifyEnabled = false
28 |
29 | private val android: BaseExtension = project.getAndroid()
30 |
31 | private lateinit var androidKlassPool: AbstractKlassPool
32 |
33 | init {
34 | project.afterEvaluate {
35 | androidKlassPool = object : AbstractKlassPool(android.bootClasspath) {}
36 | }
37 | }
38 |
39 | val bootKlassPool: AbstractKlassPool
40 | get() = androidKlassPool
41 |
42 | override fun getName() = this.javaClass.simpleName
43 |
44 | override fun isIncremental() = !verifyEnabled
45 |
46 | override fun isCacheable() = !verifyEnabled
47 |
48 | override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS
49 |
50 | override fun getScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT
51 |
52 | override fun getReferencedScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT
53 |
54 | final override fun transform(invocation: TransformInvocation) {
55 | DelegateTransformInvocation(invocation, this).apply {
56 | if (isIncremental) {
57 | doIncrementalTransform()
58 | } else {
59 | outputProvider?.deleteAll()
60 | doFullTransform()
61 | }
62 | }
63 | }
64 |
65 |
66 |
67 | }
68 |
69 | /**
70 | * The option for transform outputs verifying, default is false
71 | */
72 | private const val OPT_TRANSFORM_VERIFY = "dokit.transform.verify"
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/transform/CommonTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.transform
2 |
3 | import com.didiglobal.booster.transform.Transformer
4 | import com.lanshifu.plugin.classtransformer.AnnotationParserClassTransform
5 | import com.lanshifu.plugin.asmtransformer.BaseAsmTransformer
6 | import org.gradle.api.Project
7 |
8 | /**
9 | * @author lanxiaobin
10 | * @date 2021/11/13
11 | */
12 | class CommonTransform(androidProject: Project) : BaseTransform(androidProject) {
13 |
14 | override val transformers = listOf(
15 | BaseAsmTransformer(
16 | listOf(
17 | AnnotationParserClassTransform()
18 | )
19 | )
20 | )
21 |
22 | override fun getName(): String {
23 | return "CommonTransform"
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/com/lanshifu/plugin/transform/PrivacyMethodHookTransform.kt:
--------------------------------------------------------------------------------
1 | package com.lanshifu.plugin.transform
2 |
3 | import com.didiglobal.booster.transform.Transformer
4 | import com.didiglobal.booster.transform.asm.ClassTransformer
5 | import com.google.auto.service.AutoService
6 | import com.lanshifu.plugin.asmtransformer.BaseAsmTransformer
7 | import com.lanshifu.plugin.classtransformer.PrivacyMethodReplaceTransform
8 | import org.gradle.api.Project
9 |
10 | /**
11 | * @author lanxiaobin
12 | * @date 2021/11/13
13 | */
14 | @AutoService(ClassTransformer::class)
15 | class PrivacyMethodHookTransform(androidProject: Project) : BaseTransform(androidProject) {
16 |
17 | override val transformers = listOf(
18 | BaseAsmTransformer(
19 | listOf(
20 | PrivacyMethodReplaceTransform()
21 | )
22 | )
23 | )
24 |
25 | override fun getName(): String {
26 | return "PrivacyMethodHookTransform"
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/buildSrc/src/main/resources/META-INF/gradle-plugins/com.lanshifu.privacy_method.properties:
--------------------------------------------------------------------------------
1 | #入口文件
2 | #文件名为插件名 即 apply plugin "xxx"
3 | implementation-class=com.lanshifu.plugin.Plugin
--------------------------------------------------------------------------------
/config.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | version = [compileSdkVersion : 33,
3 | buildToolsVersion : "30.0.3",
4 | minSdkVersion : 21,
5 | targetSdkVersion : 33,
6 | versionCode : 1,
7 | androidSupportSdkVersion: "33.0.0",
8 | renderscriptTargetApi : 21,
9 | booster_version : "4.0.0",
10 | agp_plugin_verson : "7.2.2",
11 | multiDexEnabled : true,
12 | ]
13 |
14 | dependencies = [
15 | "appcompat-v7" : 'androidx.appcompat:appcompat:1.0.0',
16 | "annotations" : 'com.android.support:support-annotations:${android["androidSupportSdkVersion"]}',
17 | "support-v4" : 'androidx.legacy:legacy-support-v4:${android["androidSupportSdkVersion"]}',
18 |
19 | "appcompat" : 'androidx.appcompat:appcompat:1.0.0',
20 | "blurkit" : 'io.alterac.blurkit:blurkit:1.1.1',
21 | "crashreport" : 'com.tencent.bugly:crashreport:3.3.9',
22 | "nativecrashreport" : 'com.tencent.bugly:nativecrashreport:3.9.0',
23 |
24 | "core_ktx" : 'androidx.core:core-ktx:1.2.0',
25 | "kotlin_stdlib_jdk7" : 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10',
26 | "kotlinx_coroutines_android" : 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4',
27 |
28 | "lifecycle_viewmodel_ktx" : 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1',
29 | "lifecycle_extensions" : 'androidx.lifecycle:lifecycle-extensions:2.2.0',
30 | "recyclerview" : 'androidx.recyclerview:recyclerview:1.2.1',
31 | "BaseRecyclerViewAdapterHelper" : 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50',
32 |
33 | "okhttp3_logging_interceptor" : 'com.squareup.okhttp3:logging-interceptor:3.12.1',
34 | "autosize" : 'me.jessyan:autosize:1.2.1',
35 | "eventbus" : 'org.greenrobot:eventbus:3.2.0',
36 | "cardview" : 'androidx.cardview:cardview:1.0.0',
37 | "glide" : 'androidx.cardview:cardview:1.0.0',
38 |
39 | "smart_refresh_layout_kernel" : 'com.scwang.smart:refresh-layout-kernel:2.0.0',
40 | "smart_refresh_layout_header_material": 'com.scwang.smart:refresh-header-material:2.0.0',
41 | "smart_refresh_layout_footer_classics": 'com.scwang.smart:refresh-footer-classics:2.0.0',
42 | "doraemonkit_debug" : 'com.didichuxing.doraemonkit:dokitx:3.3.5',
43 | "doraemonkit_release" : 'com.didichuxing.doraemonkit:dokitx-no-op:3.3.5',
44 | "constraintlayout" : 'androidx.constraintlayout:constraintlayout:1.1.3',
45 | "mmkv" : 'com.tencent:mmkv-static:1.1.1',
46 |
47 | "flexbox" : 'com.google.android:flexbox:2.0.1',
48 | "rxhttp" : 'com.ljx.rxhttp:rxhttp:2.2.0',
49 | "rxhttp_compiler" : 'com.ljx.rxhttp:rxhttp-compiler:2.2.0',
50 | "xcrash" : 'com.iqiyi.xcrash:xcrash-android-lib:2.4.9',
51 | "lottie" : 'com.airbnb.android:lottie:3.4.0',
52 | "retrofit2" : 'com.squareup.retrofit2:retrofit:2.6.0',
53 | "retrofit2_converter_gson" : 'com.squareup.retrofit2:converter-gson:2.6.0',
54 | ]
55 |
56 | devSwitch = [
57 | //开发阶段true,正式发版改为false
58 | isDevMode: false,
59 | ]
60 | }
61 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanshifu/PrivacyMethodHooker/39026a944b1c5b9ea7650280057c53d3ea9a0f1c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Oct 04 20:32:36 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "PrivacyMethodHooker"
2 | include ':app'
3 | include ':asm_annotation'
4 |
--------------------------------------------------------------------------------