From 42195b9f6359b23cfa8926d62a44fe9bac93108d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 7 Jul 2025 12:29:09 +0200 Subject: [PATCH] fix: Fix accidental changes --- .../misc/spoof/EnableRomSignatureSpoofing.kt | 92 ++++++++++++++++++ .../all/misc/spoof/SignatureSpoofPatch.kt | 95 ------------------- .../spotify/misc/fix/SpoofClientPatch.kt | 90 ++++++++++++++++++ 3 files changed, 182 insertions(+), 95 deletions(-) create mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofing.kt delete mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SignatureSpoofPatch.kt diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofing.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofing.kt new file mode 100644 index 000000000..ea9136312 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofing.kt @@ -0,0 +1,92 @@ +package app.revanced.patches.all.misc.spoof + +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.patch.stringOption +import app.revanced.util.getNode +import com.android.apksig.ApkVerifier +import com.android.apksig.apk.ApkFormatException +import org.w3c.dom.Element +import java.io.File +import java.io.IOException +import java.nio.file.InvalidPathException +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory +import java.util.* + +@Suppress("unused") +val enableRomSignatureSpoofing = resourcePatch( + name = "Enable ROM signature spoofing", + description = "Spoofs the signature via the manifest meta-data \"fake-signature\". " + + "This patch only works with ROMs that support signature spoofing.", + use = false, +) { + val signatureOrPath by stringOption( + key = "signatureOrApkFilePath", + title = "Signature or APK file path", + validator = validator@{ signature -> + signature ?: return@validator false + + parseSignature(signature) != null + }, + description = "The hex-encoded signature or path to an APK file with the desired signature.", + required = true, + ) + execute { + document("AndroidManifest.xml").use { document -> + val permission = document.createElement("uses-permission").apply { + setAttribute("android:name", "android.permission.FAKE_PACKAGE_SIGNATURE") + } + val manifest = document.getNode("manifest").appendChild(permission) + + + val fakeSignatureMetadata = document.createElement("meta-data").apply { + setAttribute("android:name", "fake-signature") + setAttribute("android:value", parseSignature(signatureOrPath!!)) + } + document.getNode("application").appendChild(fakeSignatureMetadata) + } + } +} + +private fun parseSignature(optionValue: String): String? { + // Parse as a hex-encoded signature. + try { + // TODO: Replace with signature.hexToByteArray when stable in kotlin + val signatureBytes = HexFormat.of().parseHex(optionValue) + CertificateFactory.getInstance("X.509").generateCertificate(signatureBytes.inputStream()) + + return optionValue + } catch (_: IllegalArgumentException) { + } catch (_: CertificateException) { + } + + // Parse as a path to an APK file. + try { + val apkFile = File(optionValue) + if (!apkFile.isFile) return null + + val result = ApkVerifier.Builder(apkFile).build().verify() + + val hexFormat = HexFormat.of() + + val signature = (if (result.isVerifiedUsingV3Scheme) { + result.v3SchemeSigners[0].certificate + } else if (result.isVerifiedUsingV2Scheme) { + result.v2SchemeSigners[0].certificate + } else if (result.isVerifiedUsingV1Scheme) { + result.v1SchemeSigners[0].certificate + } else { + return null + }).encoded + + return hexFormat.formatHex(signature) + } catch (_: IOException) { + } catch (_: InvalidPathException) { + } catch (_: ApkFormatException) { + } catch (_: NoSuchAlgorithmException) { + } catch (_: IllegalArgumentException) { + } + + return null +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SignatureSpoofPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SignatureSpoofPatch.kt deleted file mode 100644 index 8d32bf629..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SignatureSpoofPatch.kt +++ /dev/null @@ -1,95 +0,0 @@ -package app.revanced.patches.all.misc.spoof - -import app.revanced.patcher.patch.resourcePatch -import app.revanced.patcher.patch.stringOption -import app.revanced.util.getNode -import com.android.apksig.ApkVerifier -import com.android.apksig.apk.ApkFormatException -import org.w3c.dom.Element -import java.io.ByteArrayInputStream -import java.io.IOException -import java.nio.file.Files -import java.nio.file.InvalidPathException -import java.nio.file.attribute.BasicFileAttributes -import java.security.NoSuchAlgorithmException -import java.security.cert.CertificateException -import java.security.cert.CertificateFactory -import java.util.* -import kotlin.io.path.Path - -val signatureSpoofPatch = resourcePatch( - name = "Spoof app signature", - description = "Spoofs the app signature via the \"fake-signature\" meta key. " + - "This patch only works with patched device roms.", - use = false, - ) { - val signature by stringOption( - key = "spoofedAppSignature", - title = "Signature", - validator = { signature -> - optionToSignature(signature) != null - }, - description = "The hex-encoded signature or path to an apk file with the desired signature", - required = true, - ) - execute { - document("AndroidManifest.xml").use { document -> - val manifest = document.getNode("manifest") as Element - - val fakeSignaturePermission = document.createElement("uses-permission") - fakeSignaturePermission.setAttribute("android:name", "android.permission.FAKE_PACKAGE_SIGNATURE") - manifest.appendChild(fakeSignaturePermission) - - val application = document.getNode("application") ?: { - val child = document.createElement("application") - manifest.appendChild(child) - child - } as Element; - - val fakeSignatureMetadata = document.createElement("meta-data") - fakeSignatureMetadata.setAttribute("android:name", "fake-signature") - fakeSignatureMetadata.setAttribute("android:value", optionToSignature(signature)) - application.appendChild(fakeSignatureMetadata) - } - } -} - -internal fun optionToSignature(signature: String?): String? { - if (signature == null) { - return null; - } - try { - // TODO: Replace with signature.hexToByteArray when stable in kotlin - val signatureBytes = HexFormat.of() - .parseHex(signature) - val factory = CertificateFactory.getInstance("X.509") - factory.generateCertificate(ByteArrayInputStream(signatureBytes)) - return signature; - } catch (_: IllegalArgumentException) { - } catch (_: CertificateException) { - } - try { - val signaturePath = Path(signature) - if (!Files.readAttributes(signaturePath, BasicFileAttributes::class.java).isRegularFile) { - return null; - } - val verifier = ApkVerifier.Builder(signaturePath.toFile()) - .build() - - val result = verifier.verify() - if (result.isVerifiedUsingV3Scheme) { - return HexFormat.of().formatHex(result.v3SchemeSigners[0].certificate.encoded) - } else if (result.isVerifiedUsingV2Scheme) { - return HexFormat.of().formatHex(result.v2SchemeSigners[0].certificate.encoded) - } else if (result.isVerifiedUsingV1Scheme) { - return HexFormat.of().formatHex(result.v1SchemeSigners[0].certificate.encoded) - } - - return null; - } catch (_: IOException) { - } catch (_: InvalidPathException) { - } catch (_: ApkFormatException) { - } catch (_: NoSuchAlgorithmException) { - } catch (_: IllegalArgumentException) {} - return null; -} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/fix/SpoofClientPatch.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/fix/SpoofClientPatch.kt index f40127734..c35dc1346 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/fix/SpoofClientPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/fix/SpoofClientPatch.kt @@ -37,6 +37,20 @@ val spoofClientPatch = bytecodePatch( dependsOn( sharedExtensionPatch, + hexPatch(ignoreMissingTargetFiles = true, block = fun HexPatchBuilder.() { + listOf( + "arm64-v8a", + "armeabi-v7a", + "x86", + "x86_64" + ).forEach { architecture -> + "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:$requestListenerPort/v4/login" inFile + "lib/$architecture/liborbit-jni-spotify.so" + } + }) ) compatibleWith("com.spotify.music") @@ -89,6 +103,82 @@ val spoofClientPatch = bytecodePatch( // region Spoof client. + loadOrbitLibraryFingerprint.method.addInstructions( + 0, + """ + const/16 v0, $requestListenerPort + invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->launchListener(I)V + """ + ) + + startupPageLayoutInflateFingerprint.method.apply { + val openLoginWebViewDescriptor = + "$EXTENSION_CLASS_DESCRIPTOR->launchLogin(Landroid/view/LayoutInflater;)V" + + addInstructions( + 0, + "invoke-static/range { p1 .. p1 }, $openLoginWebViewDescriptor" + ) + } + + renderStartLoginScreenFingerprint.method.apply { + val onEventIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_INTERFACE && getReference()?.name == "getView" + } + + val buttonRegister = getInstruction(onEventIndex + 1).registerA + + addInstruction( + onEventIndex + 2, + "invoke-static { v$buttonRegister }, $EXTENSION_CLASS_DESCRIPTOR->setNativeLoginHandler(Landroid/view/View;)V" + ) + } + + renderSecondLoginScreenFingerprint.method.apply { + val getViewIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_INTERFACE && getReference()?.name == "getView" + } + + val buttonRegister = getInstruction(getViewIndex + 1).registerA + + // Early return the render for loop since the first item of the loop is the login button. + addInstructions( + getViewIndex + 2, + """ + invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z + return-void + """ + ) + } + + renderThirdLoginScreenFingerprint.method.apply { + val invokeSetListenerIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Landroid/view/View;" && reference.name == "setOnClickListener" + } + + val buttonRegister = getInstruction(invokeSetListenerIndex).registerC + + addInstruction( + invokeSetListenerIndex + 1, + "invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z" + ) + } + + thirdLoginScreenLoginOnClickFingerprint.method.apply { + // Use placeholder credentials to pass the login screen. + val loginActionIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) - 1 + val loginActionInstruction = getInstruction(loginActionIndex) + + addInstructions( + loginActionIndex, + """ + const-string v${loginActionInstruction.registerD}, "placeholder" + const-string v${loginActionInstruction.registerE}, "placeholder" + """ + ) + } + // endregion // region Disable verdicts.