diff --git a/patches/api/patches.api b/patches/api/patches.api index be251e84a5..0bc30b14f9 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -125,6 +125,14 @@ public final class app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofin public static final fun getEnableROMSignatureSpoofingPatch ()Lapp/revanced/patcher/patch/Patch; } +public final class app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatchKt { + public static final fun getSpoofKeystoreSecurityLevelPatch ()Lapp/revanced/patcher/patch/Patch; +} + +public final class app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatchKt { + public static final fun getSpoofRootOfTrustPatch ()Lapp/revanced/patcher/patch/Patch; +} + public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt { public static final fun getSetTargetSDKVersion34Patch ()Lapp/revanced/patcher/patch/Patch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt index a7dfdf7bf4..cb5723de76 100644 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt @@ -6,7 +6,8 @@ import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.intOption import app.revanced.patcher.patch.stringOption -import app.revanced.patches.all.misc.transformation.transformInstructionsPatch +import app.revanced.util.forEachInstructionAsSequence +import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference @@ -33,7 +34,8 @@ val spoofSIMProviderPatch = bytecodePatch( validator = { it: String? -> it == null || it.uppercase() in countries.values }, ) - fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999) + fun isMccMncValid(it: Int?) = it == null || (it in 10000..999999) + fun isNumericValid(it: String?, length: Int) = it.isNullOrBlank() || it.equals("random", true) || it.length == length val networkCountryIso by isoCountryPatchOption("Network ISO country code") @@ -61,46 +63,119 @@ val spoofSIMProviderPatch = bytecodePatch( description = "The full name of the SIM operator.", ) + val imei by stringOption( + name = "IMEI value", + description = "15-digit IMEI to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 15) }, + ) + + val meid by stringOption( + name = "MEID value", + description = "14-char hex MEID to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 14) }, + ) + + val imsi by stringOption( + name = "IMSI (Subscriber ID)", + description = "15-digit IMSI to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 15) }, + ) + + val iccid by stringOption( + name = "ICCID (SIM Serial)", + description = "19-digit ICCID to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 19) }, + ) + + val phone by stringOption( + name = "Phone number", + description = "Phone number to spoof, blank to skip, or 'random'.", + validator = { it.isNullOrBlank() || it.equals("random", ignoreCase = true) || it.startsWith("+") }, + ) + dependsOn( - transformInstructionsPatch( - filterMap = { _, _, instruction, instructionIndex -> - if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null + bytecodePatch { + apply { + fun generateRandomNumeric(length: Int) = (1..length).map { ('0'..'9').random() }.joinToString("") - val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null - - val match = MethodCall.entries.firstOrNull { search -> - MethodUtil.methodSignaturesMatch(reference, search.reference) - } ?: return@transformInstructionsPatch null - - val replacement = when (match) { - MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase() - MethodCall.NetworkOperator -> networkOperator?.toString() - MethodCall.NetworkOperatorName -> networkOperatorName - MethodCall.SimCountryIso -> simCountryIso?.lowercase() - MethodCall.SimOperator -> simOperator?.toString() - MethodCall.SimOperatorName -> simOperatorName + fun String?.computeSpoof(randomizer: () -> String): String? { + if (this.isNullOrBlank()) return null + if (this.equals("random", ignoreCase = true)) return randomizer() + return this } - replacement?.let { instructionIndex to it } - }, - transform = ::transformMethodCall, - ), + + // Calculate the Luhn checksum (mod 10) to generate a valid 15th digit, standard for IMEI numbers. + // Structure of an IMEI is as follows: + // TAC (Type Allocation Code): First 8 digits (e.g., "86" + 6 digits) + // SNR (Serial Number): Next 6 digits + // CD (Check Digit): The 15th digit + val computedImei = imei.computeSpoof { + val prefix = "86" + generateRandomNumeric(12) + + val sum = prefix.mapIndexed { i, c -> + var d = c.digitToInt() + // Double every second digit (index 1, 3, 5...). + if (i % 2 != 0) { + d *= 2 + // If result is two digits (e.g. 14), sum them (1+4=5). + // This is mathematically equivalent to d - 9. + if (d > 9) d -= 9 + } + d + }.sum() + // Append the calculated check digit to the 14-digit prefix. + prefix + ((10 - (sum % 10)) % 10) + } + + val computedMeid = meid.computeSpoof { (1..14).map { "0123456789ABCDEF".random() }.joinToString("") }?.uppercase() + val computedImsi = imsi.computeSpoof { generateRandomNumeric(15) } + val computedIccid = iccid.computeSpoof { "89" + generateRandomNumeric(17) } + val computedPhone = phone.computeSpoof { "+" + generateRandomNumeric(11) } + + forEachInstructionAsSequence( + match = { _, _, instruction, instructionIndex -> + if (instruction !is ReferenceInstruction) return@forEachInstructionAsSequence null + + val reference = instruction.reference as? MethodReference ?: return@forEachInstructionAsSequence null + + val match = MethodCall.entries.firstOrNull { search -> + MethodUtil.methodSignaturesMatch(reference, search.reference) + } ?: return@forEachInstructionAsSequence null + + val replacement = when (match) { + MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase() + MethodCall.NetworkOperator -> networkOperator?.toString() + MethodCall.NetworkOperatorName -> networkOperatorName + MethodCall.SimCountryIso -> simCountryIso?.lowercase() + MethodCall.SimOperator -> simOperator?.toString() + MethodCall.SimOperatorName -> simOperatorName + MethodCall.Imei, MethodCall.ImeiWithSlot, MethodCall.DeviceId, MethodCall.DeviceIdWithSlot -> computedImei + MethodCall.Meid, MethodCall.MeidWithSlot -> computedMeid + MethodCall.SubscriberId, MethodCall.SubscriberIdWithSlot -> computedImsi + MethodCall.SimSerialNumber, MethodCall.SimSerialNumberWithSlot -> computedIccid + MethodCall.Line1Number, MethodCall.Line1NumberWithSlot -> computedPhone + } + replacement?.let { instructionIndex to it } + }, + transform = ::transformMethodCall + ) + } + }, ) } -private fun transformMethodCall( - mutableMethod: MutableMethod, - entry: Pair, -) { - val (instructionIndex, methodCallValue) = entry +private fun transformMethodCall(mutableMethod: MutableMethod, entry: Pair) { + val (index, value) = entry + val nextInstr = mutableMethod.getInstruction(index + 1) - // Get the register which would have contained the return value - val register = mutableMethod.getInstruction(instructionIndex + 1).registerA + if (nextInstr.opcode.name != "move-result-object") { + mutableMethod.replaceInstruction(index, "nop") + return + } - // Replace the move-result instruction with our fake value - mutableMethod.replaceInstruction( - instructionIndex + 1, - "const-string v$register, \"$methodCallValue\"", - ) + val register = (nextInstr as OneRegisterInstruction).registerA + mutableMethod.replaceInstruction(index, "const-string v$register, \"$value\"") + mutableMethod.replaceInstruction(index + 1, "nop") } private enum class MethodCall( @@ -154,4 +229,100 @@ private enum class MethodCall( "Ljava/lang/String;", ), ), + Imei( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getImei", + emptyList(), + "Ljava/lang/String;" + ), + ), + ImeiWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getImei", + listOf("I"), + "Ljava/lang/String;" + ), + ), + DeviceId( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getDeviceId", + emptyList(), + "Ljava/lang/String;" + ), + ), + DeviceIdWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getDeviceId", + listOf("I"), + "Ljava/lang/String;" + ), + ), + Meid( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getMeid", + emptyList(), + "Ljava/lang/String;" + ), + ), + MeidWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getMeid", + listOf("I"), + "Ljava/lang/String;" + ), + ), + SubscriberId( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSubscriberId", + emptyList(), + "Ljava/lang/String;" + ) + ), + SubscriberIdWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSubscriberId", + listOf("I"), + "Ljava/lang/String;" + ) + ), + SimSerialNumber( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSimSerialNumber", + emptyList(), + "Ljava/lang/String;" + ) + ), + SimSerialNumberWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSimSerialNumber", + listOf("I"), + "Ljava/lang/String;" + ) + ), + Line1Number( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getLine1Number", + emptyList(), + "Ljava/lang/String;" + ) + ), + Line1NumberWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getLine1Number", + listOf("I"), + "Ljava/lang/String;" + ) + ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt index 1666a27a8d..e00f37feaf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt @@ -3,7 +3,7 @@ package app.revanced.patches.all.misc.connectivity.wifi.spoof import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.all.misc.transformation.IMethodCall import app.revanced.patches.all.misc.transformation.filterMapInstruction35c -import app.revanced.patches.all.misc.transformation.transformInstructionsPatch +import app.revanced.util.forEachInstructionAsSequence private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch" @@ -19,29 +19,32 @@ val spoofWiFiConnectionPatch = bytecodePatch( extendWith("extensions/all/misc/connectivity/wifi/spoof/spoof-wifi.rve") dependsOn( - transformInstructionsPatch( - filterMap = { classDef, _, instruction, instructionIndex -> - filterMapInstruction35c( - EXTENSION_CLASS_DESCRIPTOR_PREFIX, - classDef, - instruction, - instructionIndex, - ) - }, - transform = { method, entry -> - val (methodType, instruction, instructionIndex) = entry - methodType.replaceInvokeVirtualWithExtension( - EXTENSION_CLASS_DESCRIPTOR, - method, - instruction, - instructionIndex, - ) - }, - ), + bytecodePatch { + apply { + forEachInstructionAsSequence( + match = { classDef, _, instruction, instructionIndex -> + filterMapInstruction35c( + EXTENSION_CLASS_DESCRIPTOR_PREFIX, + classDef, + instruction, + instructionIndex, + ) + }, + transform = { method, entry -> + val (methodType, instruction, instructionIndex) = entry + methodType.replaceInvokeVirtualWithExtension( + EXTENSION_CLASS_DESCRIPTOR, + method, + instruction, + instructionIndex, + ) + }) + } + }, ) } -// Information about method calls we want to replace +// Information about method calls we want to replace. @Suppress("unused") private enum class MethodCall( override val definedClassName: String, @@ -89,13 +92,13 @@ private enum class MethodCall( "Landroid/net/NetworkInfo;", "getState", arrayOf(), - "Landroid/net/NetworkInfo\$State;", + $$"Landroid/net/NetworkInfo$State;", ), GetDetailedState( "Landroid/net/NetworkInfo;", "getDetailedState", arrayOf(), - "Landroid/net/NetworkInfo\$DetailedState;", + $$"Landroid/net/NetworkInfo$DetailedState;", ), IsActiveNetworkMetered( "Landroid/net/ConnectivityManager;", @@ -132,7 +135,7 @@ private enum class MethodCall( "registerBestMatchingNetworkCallback", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -140,19 +143,19 @@ private enum class MethodCall( RegisterDefaultNetworkCallback1( "Landroid/net/ConnectivityManager;", "registerDefaultNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RegisterDefaultNetworkCallback2( "Landroid/net/ConnectivityManager;", "registerDefaultNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;"), "V", ), RegisterNetworkCallback1( "Landroid/net/ConnectivityManager;", "registerNetworkCallback", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RegisterNetworkCallback2( @@ -166,7 +169,7 @@ private enum class MethodCall( "registerNetworkCallback", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -174,13 +177,13 @@ private enum class MethodCall( RequestNetwork1( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RequestNetwork2( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;", "I"), "V", ), RequestNetwork3( @@ -188,7 +191,7 @@ private enum class MethodCall( "requestNetwork", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -204,7 +207,7 @@ private enum class MethodCall( "requestNetwork", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", "I", ), @@ -213,7 +216,7 @@ private enum class MethodCall( UnregisterNetworkCallback1( "Landroid/net/ConnectivityManager;", "unregisterNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), UnregisterNetworkCallback2( diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt new file mode 100644 index 0000000000..b45cfb4e0d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.all.misc.spoof + +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.forEachInstructionAsSequence + +@Suppress("unused") +val spoofKeystoreSecurityLevelPatch = bytecodePatch( + name = "Spoof keystore security level", + description = "Forces apps to see Keymaster and Attestation security levels as 'StrongBox' (Level 2).", + use = false +) { + apply { + forEachInstructionAsSequence( + match = { _, method, _, _ -> + // Match methods by comparing the current method to a reference criteria. + val name = method.name.lowercase() + if (name.contains("securitylevel") && method.returnType == "I") method else null + }, + transform = { mutableMethod, _ -> + // Ensure the method has an implementation before replacing. + if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) { + mutableMethod.replaceInstructions(0, "const/4 v0, 0x2\nreturn v0") + } + } + ) + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt new file mode 100644 index 0000000000..6177b7e317 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt @@ -0,0 +1,70 @@ +package app.revanced.patches.all.misc.spoof + +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.forEachInstructionAsSequence +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference +import com.android.tools.smali.dexlib2.util.MethodUtil + +@Suppress("unused") +val spoofRootOfTrustPatch = bytecodePatch( + name = "Spoof root of trust", + description = "Spoofs device integrity states (Locked Bootloader, Verified OS) for apps that perform local certificate attestation.", + use = false +) { + apply { + forEachInstructionAsSequence( + match = { _, method, _, _ -> + MethodCall.entries.firstOrNull { MethodUtil.methodSignaturesMatch(method, it.reference) } + }, + transform = { mutableMethod, methodCall -> + if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) { + mutableMethod.replaceInstructions(0, methodCall.replacementInstructions) + } + } + ) + } +} + +private enum class MethodCall( + val reference: MethodReference, + val replacementInstructions: String, +) { + IsDeviceLockedRootOfTrust( + ImmutableMethodReference( + "LRootOfTrust;", + "isDeviceLocked", + emptyList(), + "Z" + ), + "const/4 v0, 0x1\nreturn v0", + ), + GetVerifiedBootStateRootOfTrust( + ImmutableMethodReference( + "LRootOfTrust;", + "getVerifiedBootState", + emptyList(), + "I" + ), + "const/4 v0, 0x0\nreturn v0", + ), + IsDeviceLockedAttestation( + ImmutableMethodReference( + "LAttestation;", + "isDeviceLocked", + emptyList(), + "Z" + ), + "const/4 v0, 0x1\nreturn v0", + ), + GetVerifiedBootStateAttestation( + ImmutableMethodReference( + "LAttestation;", + "getVerifiedBootState", + emptyList(), + "I" + ), + "const/4 v0, 0x0\nreturn v0", + ), +} \ No newline at end of file