fix: Fix accidental changes

This commit is contained in:
oSumAtrIX
2025-07-07 12:29:09 +02:00
parent a4e08ea13d
commit 42195b9f63
3 changed files with 182 additions and 95 deletions

View File

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

View File

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

View File

@@ -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<MethodReference>()?.name == "getView"
}
val buttonRegister = getInstruction<OneRegisterInstruction>(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<MethodReference>()?.name == "getView"
}
val buttonRegister = getInstruction<OneRegisterInstruction>(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<MethodReference>()
reference?.definingClass == "Landroid/view/View;" && reference.name == "setOnClickListener"
}
val buttonRegister = getInstruction<FiveRegisterInstruction>(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<FiveRegisterInstruction>(loginActionIndex)
addInstructions(
loginActionIndex,
"""
const-string v${loginActionInstruction.registerD}, "placeholder"
const-string v${loginActionInstruction.registerE}, "placeholder"
"""
)
}
// endregion
// region Disable verdicts.