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
This commit is contained in:
inotia00 2025-01-15 22:02:07 +09:00
parent bda2ef23cc
commit ed6acc6197
4 changed files with 78 additions and 75 deletions

View File

@ -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;
}
}
}
}

View File

@ -33,8 +33,8 @@ import java.util.concurrent.TimeoutException
* did use its own client streams.
*/
class StreamingDataRequest private constructor(
videoId: String, playerHeaders: Map<String, String>, visitorId: String,
botGuardPoToken: String, droidGuardPoToken: String
videoId: String, playerHeaders: Map<String, String>,
visitorId: String, botGuardPoToken: String
) {
private val videoId: String
private val future: Future<ByteBuffer?>
@ -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<String, String>, visitorId: String,
botGuardPoToken: String, droidGuardPoToken: String
videoId: String, fetchHeaders: Map<String, String>,
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<String, String>,
visitorId: String, botGuardPoToken: String, droidGuardPoToken: String
clientType: AppClient.ClientType,
videoId: String,
playerHeaders: Map<String, String>,
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<String, String>, visitorId: String,
botGuardPoToken: String, droidGuardPoToken: String
videoId: String, playerHeaders: Map<String, String>,
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),

View File

@ -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 == "<init>" &&
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<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex + 1, """
invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled(Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v$insertRegister
"""
)
}
}
}
}

View File

@ -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")
)