diff --git a/README.md b/README.md index a7ebabe533..e4fa66bbd2 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Some of the features the patches provide are: * 🚫 **Block ads**: Say goodbye to ads * ⭐ **Customize your app**: Personalize the appearance of apps with various layouts and themes * 🪄 **Add new features**: Extend the functionality of apps with lots of new features +* 📡 **Protect your privacy**: Disable embedded trackers and privacy-invasive components * ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions, export activities, etc. * ✨ **And much more!** diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 35e2832131..ffa1f1b2a4 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -111,6 +111,18 @@ public final class app/revanced/patches/all/misc/versioncode/ChangeVersionCodePa public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V } +public final class app/revanced/patches/all/privacy/UniversalPrivacyPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/all/privacy/UniversalPrivacyPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + +public final class app/revanced/patches/all/privacy/UniversalResourcePrivacyPatch : app/revanced/patcher/patch/ResourcePatch { + public static final field INSTANCE Lapp/revanced/patches/all/privacy/UniversalResourcePrivacyPatch; + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V + public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V +} + public final class app/revanced/patches/all/screencapture/removerestriction/RemoveCaptureRestrictionPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch { public static final field INSTANCE Lapp/revanced/patches/all/screencapture/removerestriction/RemoveCaptureRestrictionPatch; public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object; @@ -1019,6 +1031,11 @@ public final class app/revanced/patches/shared/misc/settings/preference/TextPref public fun serialize (Lorg/w3c/dom/Document;Lkotlin/jvm/functions/Function1;)Lorg/w3c/dom/Element; } +public final class app/revanced/patches/shared/resource/AndroidManifest { + public static final field INSTANCE Lapp/revanced/patches/shared/resource/AndroidManifest; + public final fun addMetadata (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V +} + public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryPatch.kt b/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryPatch.kt new file mode 100644 index 0000000000..21f68366f2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryPatch.kt @@ -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) + } +} diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryThroughManifestPatch.kt b/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryThroughManifestPatch.kt new file mode 100644 index 0000000000..b1ec8ae526 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/DisableAnalyticsAndTelemetryThroughManifestPatch.kt @@ -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") + + 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) = + metaDataNodes.forEach { (nodeName, nodeValue) -> + val applicationNode = getNode("application") as Element + applicationNode.getElementsByTagName(META_DATA_TAG) + .asSequence() + .first { it.attributes.getNamedItem(NAME_ATTRIBUTE).nodeValue == nodeName } + .attributes.getNamedItem(VALUE_ATTRIBUTE).nodeValue = nodeValue + } +} diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/PatchExtension.kt b/src/main/kotlin/app/revanced/patches/all/privacy/PatchExtension.kt new file mode 100644 index 0000000000..69363c76b1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/PatchExtension.kt @@ -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> { _, property -> + booleanPatchOption(key = property.name, default = true, title = title, required = true) +} diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/BuildSegmentFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/BuildSegmentFingerprint.kt new file mode 100644 index 0000000000..bad5f2c41f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/BuildSegmentFingerprint.kt @@ -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."), +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/CallSettingsSpiFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/CallSettingsSpiFingerprint.kt new file mode 100644 index 0000000000..17f653ddf4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/CallSettingsSpiFingerprint.kt @@ -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" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/DoConfigFetchFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/DoConfigFetchFingerprint.kt new file mode 100644 index 0000000000..2fdf1e55a1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/DoConfigFetchFingerprint.kt @@ -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" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAmplitudeFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAmplitudeFingerprint.kt new file mode 100644 index 0000000000..f419c9abd9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAmplitudeFingerprint.kt @@ -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()"), +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAnalyticsFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAnalyticsFingerprint.kt new file mode 100644 index 0000000000..d723c50236 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAnalyticsFingerprint.kt @@ -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 + }, +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAppsFlyerSDKFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAppsFlyerSDKFingerprint.kt new file mode 100644 index 0000000000..d4a5c2aec1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitAppsFlyerSDKFingerprint.kt @@ -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)"), +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitSDKFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitSDKFingerprint.kt new file mode 100644 index 0000000000..d5c82f0a9c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitSDKFingerprint.kt @@ -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"), +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeAsyncStatsigClientFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeAsyncStatsigClientFingerprint.kt new file mode 100644 index 0000000000..1ef4c9e0d5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeAsyncStatsigClientFingerprint.kt @@ -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" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeSdkFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeSdkFingerprint.kt new file mode 100644 index 0000000000..e6cfe0ed7d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/InitializeSdkFingerprint.kt @@ -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 + +// Matches com.moengage.core.internal.initialisation.initialiseSdk. +internal object InitializeSdkFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf("moEngage", "App-Id is empty, SDK cannot be initialised."), + customFingerprint = { _, classDef -> + classDef.sourceFile == "InitialisationHandler.kt" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SendFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SendFingerprint.kt new file mode 100644 index 0000000000..596e71ecee --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SendFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.all.privacy.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object SendFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC.value, + parameters = listOf("L", "L"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT + ), + customFingerprint = { _, classDef -> + classDef.sourceFile == "TransportRuntime.java" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SetupComsCoreFingerprint.kt b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SetupComsCoreFingerprint.kt new file mode 100644 index 0000000000..49d1cc833f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/privacy/fingerprints/SetupComsCoreFingerprint.kt @@ -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 SetupComsCoreFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, + customFingerprint = { methodDef, classDef -> + classDef.type == "Lcom/comscore/util/setup/Setup;" && methodDef.name == "setUp" + }, +)