fix(Spotify - Spoof client): Handle remaining edge cases to obtain a session (#5285)

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
This commit is contained in:
oSumAtrIX
2025-07-01 23:11:05 +02:00
committed by GitHub
parent d3ec219a29
commit b2e601f0f0
13 changed files with 262 additions and 169 deletions

View File

@@ -87,8 +87,8 @@ fun sharedExtensionPatch(
class ExtensionHook internal constructor(
internal val fingerprint: Fingerprint,
private val insertIndexResolver: ((Method) -> Int),
private val contextRegisterResolver: (Method) -> String,
private val insertIndexResolver: BytecodePatchContext.(Method) -> Int,
private val contextRegisterResolver: BytecodePatchContext.(Method) -> String,
) {
context(BytecodePatchContext)
operator fun invoke(extensionClassDescriptor: String) {
@@ -98,19 +98,19 @@ class ExtensionHook internal constructor(
fingerprint.method.addInstruction(
insertIndex,
"invoke-static/range { $contextRegister .. $contextRegister }, " +
"$extensionClassDescriptor->setContext(Landroid/content/Context;)V",
"$extensionClassDescriptor->setContext(Landroid/content/Context;)V",
)
}
}
fun extensionHook(
insertIndexResolver: ((Method) -> Int) = { 0 },
contextRegisterResolver: (Method) -> String = { "p0" },
insertIndexResolver: BytecodePatchContext.(Method) -> Int = { 0 },
contextRegisterResolver: BytecodePatchContext.(Method) -> String = { "p0" },
fingerprint: Fingerprint,
) = ExtensionHook(fingerprint, insertIndexResolver, contextRegisterResolver)
fun extensionHook(
insertIndexResolver: ((Method) -> Int) = { 0 },
contextRegisterResolver: (Method) -> String = { "p0" },
insertIndexResolver: BytecodePatchContext.(Method) -> Int = { 0 },
contextRegisterResolver: BytecodePatchContext.(Method) -> String = { "p0" },
fingerprintBuilderBlock: FingerprintBuilder.() -> Unit,
) = extensionHook(insertIndexResolver, contextRegisterResolver, fingerprint(block = fingerprintBuilderBlock))

View File

@@ -2,4 +2,8 @@ package app.revanced.patches.spotify.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch("spotify", mainActivityOnCreateHook)
val sharedExtensionPatch = sharedExtensionPatch(
"spotify",
mainActivityOnCreateHook,
loadOrbitLibraryHook
)

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.spotify.misc.extension
import app.revanced.patcher.fingerprint
internal val loadOrbitLibraryFingerprint = fingerprint {
strings("OrbitLibraryLoader", "cst")
}

View File

@@ -1,6 +1,26 @@
package app.revanced.patches.spotify.misc.extension
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patches.shared.misc.extension.extensionHook
import app.revanced.patches.spotify.shared.mainActivityOnCreateFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal val mainActivityOnCreateHook = extensionHook(fingerprint = mainActivityOnCreateFingerprint)
internal val loadOrbitLibraryHook = extensionHook(
insertIndexResolver = {
loadOrbitLibraryFingerprint.stringMatches!!.last().index
},
contextRegisterResolver = { method ->
val contextReferenceIndex = method.indexOfFirstInstruction {
getReference<FieldReference>()?.type == "Landroid/content/Context;"
}
val contextRegister = method.getInstruction<TwoRegisterInstruction>(contextReferenceIndex).registerA
"v$contextRegister"
},
fingerprint = loadOrbitLibraryFingerprint,
)

View File

@@ -1,7 +1,11 @@
package app.revanced.patches.spotify.misc.fix
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val getPackageInfoFingerprint = fingerprint {
strings(
@@ -9,7 +13,7 @@ internal val getPackageInfoFingerprint = fingerprint {
)
}
internal val startLiborbitFingerprint = fingerprint {
internal val loadOrbitLibraryFingerprint = fingerprint {
strings("/liborbit-jni-spotify.so")
}
@@ -20,10 +24,22 @@ internal val startupPageLayoutInflateFingerprint = fingerprint {
strings("blueprintContainer", "gradient", "valuePropositionTextView")
}
internal val standardIntegrityTokenProviderBuilderFingerprint = fingerprint {
strings(
"standard_pi_init",
"outcome",
"success"
internal val runIntegrityVerificationFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
opcodes(
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.INVOKE_STATIC, // Calendar.getInstance()
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL, // instance.get(6)
Opcode.MOVE_RESULT,
Opcode.IF_EQ, // if (x == instance.get(6)) return
)
custom { method, _ ->
method.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
reference?.definingClass == "Ljava/util/Calendar;" && reference.name == "get"
} >= 0
}
}

View File

@@ -24,8 +24,8 @@ val spoofClientPatch = bytecodePatch(
name = "Spoof client",
description = "Spoofs the client to fix various functions of the app.",
) {
val port by intOption(
key = "port",
val requestListenerPort by intOption(
key = "requestListenerPort",
default = 4345,
title = " Login request listener port",
description = "The port to use for the listener that intercepts and handles login requests. " +
@@ -46,10 +46,10 @@ val spoofClientPatch = bytecodePatch(
"x86",
"x86_64"
).forEach { architecture ->
"https://login5.spotify.com/v3/login" to "http://127.0.0.1:$port/v3/login" inFile
"https://login5.spotify.com/v3/login" to "http://127.0.0.1:$requestListenerPort/v3/login" inFile
"lib/$architecture/liborbit-jni-spotify.so"
"https://login5.spotify.com/v4/login" to "http://127.0.0.1:$port/v4/login" inFile
"https://login5.spotify.com/v4/login" to "http://127.0.0.1:$requestListenerPort/v4/login" inFile
"lib/$architecture/liborbit-jni-spotify.so"
}
})
@@ -58,6 +58,8 @@ val spoofClientPatch = bytecodePatch(
compatibleWith("com.spotify.music")
execute {
// region Spoof package info.
getPackageInfoFingerprint.method.apply {
// region Spoof signature.
@@ -99,28 +101,33 @@ val spoofClientPatch = bytecodePatch(
// endregion
}
startLiborbitFingerprint.method.addInstructions(
// endregion
// region Spoof client.
loadOrbitLibraryFingerprint.method.addInstructions(
0,
"""
const/16 v0, $port
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->listen(I)V
const/16 v0, $requestListenerPort
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->launchListener(I)V
"""
)
startupPageLayoutInflateFingerprint.method.apply {
val openLoginWebViewDescriptor =
"$EXTENSION_CLASS_DESCRIPTOR->login(Landroid/view/LayoutInflater;)V"
"$EXTENSION_CLASS_DESCRIPTOR->launchLogin(Landroid/view/LayoutInflater;)V"
addInstructions(
0,
"""
move-object/from16 v3, p1
invoke-static { v3 }, $openLoginWebViewDescriptor
invoke-static/range { p1 .. p1 }, $openLoginWebViewDescriptor
"""
)
}
// Early return to block sending bad verdicts to the API.
standardIntegrityTokenProviderBuilderFingerprint.method.returnEarly()
runIntegrityVerificationFingerprint.method.returnEarly()
// endregion
}
}

View File

@@ -2,7 +2,7 @@ package app.revanced.patches.spotify.shared
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.spotify.misc.extension.mainActivityOnCreateHook
import com.android.tools.smali.dexlib2.AccessFlags
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
@@ -12,6 +12,9 @@ private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivit
internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;"
internal val mainActivityOnCreateFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("Landroid/os/Bundle;")
custom { method, classDef ->
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
@@ -26,9 +29,10 @@ private var isLegacyAppTarget: Boolean? = null
* supports Spotify integration on Kenwood/Pioneer car stereos.
*/
context(BytecodePatchContext)
internal val IS_SPOTIFY_LEGACY_APP_TARGET get(): Boolean {
if (isLegacyAppTarget == null) {
isLegacyAppTarget = mainActivityOnCreateHook.fingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
internal val IS_SPOTIFY_LEGACY_APP_TARGET
get(): Boolean {
if (isLegacyAppTarget == null) {
isLegacyAppTarget = mainActivityOnCreateFingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
}
return isLegacyAppTarget!!
}
return isLegacyAppTarget!!
}