From ed6acc6197e8f2548f4b38be46b180497af5256a Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:02:07 +0900 Subject: [PATCH] feat(Spoof streaming data): Disable PoToken service when is turned on - Any app clients that use do not need the PoToken generated by DroidGuard - is fetched even when is turned on - DroidGuard VM tries to solve , but some functions are not fully implemented in DroidGuard, resulting in spam logs: https://github.com/microg/GmsCore/issues/2584 --- .../spoof/SpoofStreamingDataPatch.java | 31 ++++------ .../spoof/requests/StreamingDataRequest.kt | 33 +++++------ .../BaseSpoofStreamingDataPatch.kt | 56 ++++++++++++------- .../spoof/streamingdata/Fingerprints.kt | 33 ++++++----- 4 files changed, 78 insertions(+), 75 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java index 6bf264184..7361c97d5 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java @@ -4,7 +4,6 @@ import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDa import android.net.Uri; import android.text.TextUtils; -import android.util.Base64; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,9 +34,6 @@ public class SpoofStreamingDataPatch { private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING); - @NonNull - private static volatile String droidGuardPoToken = ""; - /** * Key: video id * Value: original video length [streamingData.formats.approxDurationMs] @@ -111,6 +107,16 @@ public class SpoofStreamingDataPatch { return SPOOF_STREAMING_DATA; } + /** + * Injection point. + */ + public static Object isSpoofingEnabled(Object original) { + if (!SPOOF_STREAMING_DATA) { + return original; + } + return null; + } + /** * Injection point. * This method is only invoked when playing a livestream on an iOS client. @@ -173,7 +179,7 @@ public class SpoofStreamingDataPatch { if (SPOOF_STREAMING_DATA_MUSIC) { try { if (requestHeader != null) { - StreamingDataRequest.fetchRequest(videoId, requestHeader, VISITOR_DATA, PO_TOKEN, droidGuardPoToken); + StreamingDataRequest.fetchRequest(videoId, requestHeader, VISITOR_DATA, PO_TOKEN); } else { Logger.printDebug(() -> "Ignoring request with no header."); } @@ -212,7 +218,7 @@ public class SpoofStreamingDataPatch { return; } - StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN, droidGuardPoToken); + StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN); } catch (Exception ex) { Logger.printException(() -> "fetchStreams failure", ex); } @@ -335,17 +341,4 @@ public class SpoofStreamingDataPatch { return videoFormat; } - - /** - * Injection point. - */ - public static void setDroidGuardPoToken(byte[] bytes) { - if (SPOOF_STREAMING_DATA && bytes.length > 20) { - final String poToken = Base64.encodeToString(bytes, Base64.URL_SAFE); - if (!droidGuardPoToken.equals(poToken)) { - Logger.printDebug(() -> "New droidGuardPoToken loaded:\n" + poToken); - droidGuardPoToken = poToken; - } - } - } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt index 82960f1a7..6de44f731 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt @@ -33,8 +33,8 @@ import java.util.concurrent.TimeoutException * did use its own client streams. */ class StreamingDataRequest private constructor( - videoId: String, playerHeaders: Map, visitorId: String, - botGuardPoToken: String, droidGuardPoToken: String + videoId: String, playerHeaders: Map, + visitorId: String, botGuardPoToken: String ) { private val videoId: String private val future: Future @@ -47,8 +47,7 @@ class StreamingDataRequest private constructor( videoId, playerHeaders, visitorId, - botGuardPoToken, - droidGuardPoToken + botGuardPoToken ) } } @@ -136,8 +135,8 @@ class StreamingDataRequest private constructor( @JvmStatic fun fetchRequest( - videoId: String, fetchHeaders: Map, visitorId: String, - botGuardPoToken: String, droidGuardPoToken: String + videoId: String, fetchHeaders: Map, + visitorId: String, botGuardPoToken: String ) { // Always fetch, even if there is an existing request for the same video. cache[videoId] = @@ -145,8 +144,7 @@ class StreamingDataRequest private constructor( videoId, fetchHeaders, visitorId, - botGuardPoToken, - droidGuardPoToken + botGuardPoToken ) } @@ -160,8 +158,11 @@ class StreamingDataRequest private constructor( } private fun send( - clientType: AppClient.ClientType, videoId: String, playerHeaders: Map, - visitorId: String, botGuardPoToken: String, droidGuardPoToken: String + clientType: AppClient.ClientType, + videoId: String, + playerHeaders: Map, + visitorId: String, + botGuardPoToken: String ): HttpURLConnection? { Objects.requireNonNull(clientType) Objects.requireNonNull(videoId) @@ -210,10 +211,7 @@ class StreamingDataRequest private constructor( visitorId = visitorId, setLocale = setLocale ) - if (droidGuardPoToken.isNotEmpty()) { - Logger.printDebug { "Original poToken (droidGuardPoToken):\n$droidGuardPoToken" } - } - Logger.printDebug { "Replaced poToken (botGuardPoToken):\n$botGuardPoToken" } + Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" } } else { requestBody = createApplicationRequestBody( @@ -249,8 +247,8 @@ class StreamingDataRequest private constructor( } private fun fetch( - videoId: String, playerHeaders: Map, visitorId: String, - botGuardPoToken: String, droidGuardPoToken: String + videoId: String, playerHeaders: Map, + visitorId: String, botGuardPoToken: String ): ByteBuffer? { lastSpoofedClientType = null @@ -267,8 +265,7 @@ class StreamingDataRequest private constructor( videoId, playerHeaders, visitorId, - botGuardPoToken, - droidGuardPoToken + botGuardPoToken )?.let { connection -> try { // gzip encoding doesn't response with content length (-1), diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt index 6d9012f87..5ba50bab4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt @@ -21,6 +21,7 @@ import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.mutableClassOrThrow import app.revanced.util.getReference +import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -103,9 +104,9 @@ fun baseSpoofStreamingDataPatch( "fetchStreams(Ljava/lang/String;Ljava/util/Map;)V" if (entrySetIndex < 0) smaliInstructions = """ - move-object/from16 v$mapRegister, p1 - - """ + smaliInstructions + move-object/from16 v$mapRegister, p1 + + """ + smaliInstructions // Copy request headers for streaming data fetch. addInstructions(newRequestBuilderIndex + 2, smaliInstructions) @@ -366,25 +367,38 @@ fun baseSpoofStreamingDataPatch( // endregion - // region Set DroidGuard poToken. - - poTokenToStringFingerprint.mutableClassOrThrow().let { - val poTokenClass = it.fields.find { field -> - field.accessFlags == AccessFlags.PRIVATE.value && field.type.startsWith("L") - }!!.type - - findMethodOrThrow(poTokenClass) { - name == "" && - parameters == listOf("[B") - }.addInstruction( - 1, - "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->setDroidGuardPoToken([B)V" - ) - } - - // endregion - executeBlock() } + + finalize { + gmsServiceBrokerFingerprint.methodOrThrow() + .addInstructionsWithLabels( + 0, """ + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z + move-result v0 + if-eqz v0, :ignore + return-void + :ignore + nop + """ + ) + + gmsServiceBrokerExceptionFingerprint.matchOrThrow().let { + val walkerIndex = it.patternMatch!!.startIndex + val walkerMethod = it.getWalkerMethod(walkerIndex) + + walkerMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.CHECK_CAST) + val insertRegister = getInstruction(insertIndex).registerA + + addInstructions( + insertIndex + 1, """ + invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled(Ljava/lang/Object;)Ljava/lang/Object; + move-result-object v$insertRegister + """ + ) + } + } + } } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt index 265b1c245..8be26a661 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt @@ -198,21 +198,20 @@ internal val hlsCurrentTimeFingerprint = legacyFingerprint( literals = listOf(HLS_CURRENT_TIME_FEATURE_FLAG), ) -internal val poTokenToStringFingerprint = legacyFingerprint( - name = "poTokenToStringFingerprint", - returnType = "Ljava/lang/String;", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = emptyList(), - strings = listOf("UTF-8"), - customFingerprint = { method, classDef -> - method.name == "toString" && - classDef.fields.find { it.type == "[B" } != null && - // In YouTube, this field's type is 'Lcom/google/android/gms/potokens/PoToken;'. - // In YouTube Music, this class name is obfuscated. - classDef.fields.find { - it.accessFlags == AccessFlags.PRIVATE.value && it.type.startsWith( - "L" - ) - } != null - }, +internal val gmsServiceBrokerFingerprint = legacyFingerprint( + name = "gmsServiceBrokerFingerprint", + returnType = "V", + strings = listOf("mServiceBroker is null, client disconnected") ) + +internal val gmsServiceBrokerExceptionFingerprint = legacyFingerprint( + name = "gmsServiceBrokerExceptionFingerprint", + returnType = "V", + parameters = listOf("Ljava/lang/Exception;"), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ), + strings = listOf("Exception must not be null") +) +