-
-
Notifications
You must be signed in to change notification settings - Fork 317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add Disable analytics and telemetry
patches
#3448
base: dev
Are you sure you want to change the base?
Changes from all commits
b137a21
bd5c925
f3c3706
63801d3
313e2b5
96fe5d0
a8b6b82
be63a06
1d4b437
bf5a1f3
e5bda0d
e38d437
d396637
2885981
6926e07
84d7c64
574017c
a592e7c
a02dd18
6b80eb8
e36b41d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package app.revanced.patches.all.privacy | ||
|
||
import app.revanced.patcher.data.BytecodeContext | ||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions | ||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels | ||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction | ||
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions | ||
import app.revanced.patcher.patch.BytecodePatch | ||
import app.revanced.patcher.patch.PatchException | ||
import app.revanced.patcher.patch.annotation.Patch | ||
import app.revanced.patcher.util.smali.ExternalLabel | ||
import app.revanced.patches.all.privacy.fingerprints.* | ||
import app.revanced.util.resultOrThrow | ||
import java.util.logging.Logger | ||
|
||
@Patch( | ||
name = "Disable analytics and telemetry", | ||
description = "Patches the app to disable analytics and telemetry services.", | ||
use = false, | ||
) | ||
@Suppress("unused") | ||
object DisableAnalyticsAndTelemetryPatch : BytecodePatch( | ||
setOf( | ||
InitializeAsyncStatsigClientFingerprint, | ||
InitAnalyticsFingerprint, | ||
InitAppsFlyerSDKFingerprint, | ||
SetupComsCoreFingerprint, | ||
CallSettingsSpiFingerprint, | ||
DoConfigFetchFingerprint, | ||
SendFingerprint, | ||
InitializeSdkFingerprint, | ||
BuildSegmentFingerprint, | ||
InitSDKFingerprint, | ||
InitAmplitudeFingerprint, | ||
), | ||
) { | ||
private val logger = Logger.getLogger(this::class.java.name) | ||
|
||
private val disableGoogleAnalytics by option("Google Analytics") | ||
private val disableAmplitude by option("Amplitude") | ||
private val disableStatsig by option("Statsig") | ||
private val disableAppsFlyerSDK by option("Apps Flyer SDK") | ||
private val disableAppsFlyerPlugin by option("Apps Flyer plugin") | ||
private val disableComsCore by option("ComsCore") | ||
private val disableCrashlytics by option("Crashlytics") | ||
private val disableFirebaseTransport by option("Firebase Transport") | ||
private val disableMoEngage by option("MoEngage") | ||
private val disableSegment by option("Segment") | ||
|
||
override fun execute(context: BytecodeContext) = mapOf( | ||
disableAmplitude to { | ||
InitAmplitudeFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-object p0", | ||
) | ||
}, | ||
disableStatsig to { | ||
InitializeAsyncStatsigClientFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-void", | ||
) | ||
}, | ||
disableAppsFlyerSDK to { | ||
InitAppsFlyerSDKFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-object p0", | ||
) | ||
}, | ||
disableAppsFlyerPlugin to { | ||
InitSDKFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-void", | ||
) | ||
}, | ||
disableComsCore to { | ||
SetupComsCoreFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-void", | ||
) | ||
}, | ||
// Neutralize the method sending data to the backend. | ||
disableFirebaseTransport to { | ||
SendFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"return-void", | ||
) | ||
}, | ||
// Empties the "context" argument to force an exception. | ||
disableGoogleAnalytics to { | ||
InitAnalyticsFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"const/4 p0, 0x0", | ||
) | ||
}, | ||
// Empties the writeKey parameter to abort initialization | ||
disableSegment to { | ||
BuildSegmentFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
"const-string p2, \"\"", | ||
) | ||
}, | ||
// Neutralize the two methods responsible for requesting Crashlytics' configuration. | ||
// which effectively disables the SDK. | ||
disableCrashlytics to { | ||
CallSettingsSpiFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
""" | ||
const/4 p1, 0x0 | ||
return-object p1 | ||
""", | ||
) | ||
|
||
DoConfigFetchFingerprint.resultOrThrow().mutableMethod.apply { | ||
// Jumps to the end of the method to directly return an empty object. | ||
addInstructionsWithLabels( | ||
0, | ||
""" | ||
goto :return_unit | ||
""", | ||
ExternalLabel("return_unit", getInstruction(getInstructions().lastIndex - 2)), | ||
) | ||
} | ||
}, | ||
disableMoEngage to { | ||
InitializeSdkFingerprint.resultOrThrow().mutableMethod.addInstructions( | ||
0, | ||
""" | ||
const/4 v0, 0x0 | ||
return-object v0 | ||
""", | ||
) | ||
}, | ||
).forEach { (option, patch) -> | ||
val isEnabled by option | ||
if (!isEnabled!!) return@forEach | ||
|
||
val message = try { | ||
patch() | ||
|
||
"Disabled ${option.title}" | ||
} catch (exception: PatchException) { | ||
"${option.title} was not found. Skipping." | ||
} | ||
|
||
logger.info(message) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package app.revanced.patches.all.privacy | ||
|
||
import app.revanced.patcher.data.ResourceContext | ||
import app.revanced.patcher.patch.PatchException | ||
import app.revanced.patcher.patch.ResourcePatch | ||
import app.revanced.patcher.patch.annotation.Patch | ||
import app.revanced.util.asSequence | ||
import app.revanced.util.getNode | ||
import org.w3c.dom.Document | ||
import org.w3c.dom.Element | ||
import java.util.logging.Logger | ||
|
||
@Patch( | ||
name = "Disable analytics and telemetry through manifest", | ||
description = "Patches the manifest to disable analytics and telemetry services.", | ||
use = false, | ||
) | ||
@Suppress("unused") | ||
object DisableAnalyticsAndTelemetryThroughManifestPatch : ResourcePatch() { | ||
private val logger = Logger.getLogger(this::class.java.name) | ||
|
||
private val disableFirebaseTelemetry by option("Firebase telemetry") | ||
private val disableFacebookSdk by option("Facebook SDK") | ||
private val disableGoogleAnalytics by option("Google Analytics") | ||
Comment on lines
+22
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GAnalytics patch also exists in the bytecode patch, why is it present here as well? Also, can't the firebase & facebook patches be converted to bytecode? |
||
|
||
override fun execute(context: ResourceContext) { | ||
context.document["AndroidManifest.xml"].use { document -> | ||
mapOf( | ||
disableFirebaseTelemetry to { | ||
document.addMetadata( | ||
"firebase_analytics_collection_enabled" to "false", | ||
"firebase_analytics_collection_deactivated" to "true", | ||
"firebase_crashlytics_collection_enabled" to "false", | ||
"firebase_performance_collection_enabled" to "false", | ||
"firebase_performance_collection_deactivated" to "true", | ||
"firebase_data_collection_default_enabled" to "false", | ||
) | ||
}, | ||
disableFacebookSdk to { | ||
document.addMetadata( | ||
"com.facebook.sdk.AutoLogAppEventsEnabled" to "false", | ||
"com.facebook.sdk.AdvertiserIDCollectionEnabled" to "false", | ||
"com.facebook.sdk.MonitorEnabled" to "false", | ||
"com.facebook.sdk.AutoInitEnabled" to "false", | ||
) | ||
}, | ||
disableGoogleAnalytics to { | ||
document.addMetadata( | ||
"google_analytics_adid_collection_enabled" to "false", | ||
"google_analytics_default_allow_ad_personalization_signals" to "false", | ||
"google_analytics_automatic_screen_reporting_enabled" to "false", | ||
"google_analytics_default_allow_ad_storage" to "false", | ||
"google_analytics_default_allow_ad_user_data" to "false", | ||
"google_analytics_default_allow_analytics_storage" to "false", | ||
"google_analytics_sgtm_upload_enabled" to "false", | ||
"google_analytics_deferred_deep_link_enabled" to "false", | ||
) | ||
}, | ||
).forEach { option, patch -> | ||
val isEnabled by option | ||
if (!isEnabled!!) return@forEach | ||
|
||
val message = try { | ||
patch() | ||
|
||
"Disabled ${option.title}" | ||
} catch (exception: PatchException) { | ||
"${option.title} was not found. Skipping." | ||
} | ||
|
||
logger.info(message) | ||
} | ||
} | ||
} | ||
|
||
private const val META_DATA_TAG = "meta-data" | ||
private const val NAME_ATTRIBUTE = "android:name" | ||
private const val VALUE_ATTRIBUTE = "android:value" | ||
|
||
private fun Document.addMetadata(vararg metaDataNodes: Pair<String, String>) = | ||
metaDataNodes.forEach { (nodeName, nodeValue) -> | ||
val applicationNode = getNode("application") as Element | ||
applicationNode.getElementsByTagName(META_DATA_TAG) | ||
.asSequence() | ||
.first { it.attributes.getNamedItem(NAME_ATTRIBUTE).nodeValue == nodeName } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I replaced firstOrNull with first here when refactoring. Correct this if it should be firstOrNull. If it should, then the try captch block above isn't necessary which was present before I refactored. |
||
.attributes.getNamedItem(VALUE_ATTRIBUTE).nodeValue = nodeValue | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package app.revanced.patches.all.privacy | ||
|
||
import app.revanced.patcher.patch.Patch | ||
import app.revanced.patcher.patch.options.PatchOption | ||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption | ||
import kotlin.properties.ReadOnlyProperty | ||
|
||
internal fun Patch<*>.option( | ||
title: String, | ||
) = ReadOnlyProperty<Any?, PatchOption<Boolean?>> { _, property -> | ||
booleanPatchOption(key = property.name, default = true, title = title, required = true) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object BuildSegmentFingerprint : MethodFingerprint( | ||
returnType = "V", | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, | ||
parameters = listOf("Landroid/content/Context;", "Ljava/lang/String;"), | ||
strings = listOf("writeKey must not be empty."), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object CallSettingsSpiFingerprint : MethodFingerprint( | ||
returnType = "L", | ||
accessFlags = AccessFlags.PUBLIC.value, | ||
strings = listOf("Settings request failed."), | ||
customFingerprint = { _, classDef -> | ||
classDef.sourceFile == "DefaultSettingsSpiCall.java" | ||
}, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
// Matches is com.google.firebase.sessions.settings.RemoteSettingsFetcher.doConfigFetch. | ||
internal object DoConfigFetchFingerprint : MethodFingerprint( | ||
returnType = "L", | ||
accessFlags = AccessFlags.PUBLIC.value, | ||
parameters = listOf("Ljava/util/Map;", "L", "L", "L"), | ||
customFingerprint = { _, classDef -> | ||
classDef.sourceFile == "RemoteSettingsFetcher.kt" | ||
}, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object InitAmplitudeFingerprint : MethodFingerprint( | ||
returnType = "L", | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.DECLARED_SYNCHRONIZED, | ||
parameters = listOf("Landroid/content/Context;", "Ljava/lang/String;", "Ljava/lang/String;", "Ljava/lang/String;", "Z", "L"), | ||
strings = listOf("Argument context cannot be null in initialize()", "Argument apiKey cannot be null or blank in initialize()"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object InitAnalyticsFingerprint : MethodFingerprint( | ||
returnType = "L", | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, | ||
parameters = listOf("Landroid/content/Context;"), | ||
strings = listOf("Slow initialization (ms)"), | ||
customFingerprint = { _, classDef -> | ||
classDef.sourceFile?.startsWith("com.google.android.gms:play-services-analytics-impl") == true | ||
}, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
// Matches com.appsflyer.internal.AFb1vSDK.init. | ||
internal object InitAppsFlyerSDKFingerprint : MethodFingerprint( | ||
returnType = "L", | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, | ||
parameters = listOf("L", "L", "L"), | ||
strings = listOf("Initializing AppsFlyer SDK: (v%s.%s)"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object InitSDKFingerprint : MethodFingerprint( | ||
returnType = "V", | ||
accessFlags = AccessFlags.PRIVATE.value, | ||
parameters = listOf("L", "L"), | ||
strings = listOf("manualStart", "afDevKey", "AF Dev Key is empty"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package app.revanced.patches.all.privacy.fingerprints | ||
|
||
import app.revanced.patcher.extensions.or | ||
import app.revanced.patcher.fingerprint.MethodFingerprint | ||
import com.android.tools.smali.dexlib2.AccessFlags | ||
|
||
internal object InitializeAsyncStatsigClientFingerprint : MethodFingerprint( | ||
returnType = "V", | ||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, | ||
customFingerprint = { methodDef, classDef -> | ||
classDef.sourceFile == "StatsigClient.kt" && methodDef.name == "initializeAsync" | ||
}, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I merged the two firebase patches to one.