From adce206d66e1f7017328fe68a5818d424f70e588 Mon Sep 17 00:00:00 2001 From: Linus <23507341+Linus789@users.noreply.github.com> Date: Wed, 1 Feb 2023 19:52:41 +0000 Subject: [PATCH] feat: `spoof-wifi-connection` patch (#1527) Signed-off-by: oSumAtrIX Co-authored-by: Linus789 Co-authored-by: oSumAtrIX --- .../wifi/spoof/patch/SpoofWifiPatch.kt | 206 ++++++++++++++++++ .../patch/RemoveScreenshotRestrictionPatch.kt | 111 +++------- .../AbstractTransformInstructionsPatch.kt | 63 ++++++ .../app/revanced/util/patch/MethodCall.kt | 92 ++++++++ 4 files changed, 392 insertions(+), 80 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/all/connectivity/wifi/spoof/patch/SpoofWifiPatch.kt create mode 100644 src/main/kotlin/app/revanced/util/patch/AbstractTransformInstructionsPatch.kt create mode 100644 src/main/kotlin/app/revanced/util/patch/MethodCall.kt diff --git a/src/main/kotlin/app/revanced/patches/all/connectivity/wifi/spoof/patch/SpoofWifiPatch.kt b/src/main/kotlin/app/revanced/patches/all/connectivity/wifi/spoof/patch/SpoofWifiPatch.kt new file mode 100644 index 000000000..0c6bdeae8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/all/connectivity/wifi/spoof/patch/SpoofWifiPatch.kt @@ -0,0 +1,206 @@ +package app.revanced.patches.all.connectivity.wifi.spoof.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.util.patch.* +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.Instruction +import java.util.* + +@Patch(false) +@Name("spoof-wifi-connection") +@Description("Spoofs an existing Wi-Fi connection.") +@Version("0.0.1") +internal class SpoofWifiPatch : AbstractTransformInstructionsPatch() { + + private companion object { + const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/all/connectivity/wifi/spoof/SpoofWifiPatch" + const val INTEGRATIONS_CLASS_DESCRIPTOR = "${INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX};" + } + + // Information about method calls we want to replace + enum class MethodCall( + override val definedClassName: String, + override val methodName: String, + override val methodParams: Array, + override val returnType: String, + ): IMethodCall { + GetSystemService1( + "Landroid/content/Context;", + "getSystemService", + arrayOf("Ljava/lang/String;"), + "Ljava/lang/Object;", + ), + GetSystemService2( + "Landroid/content/Context;", + "getSystemService", + arrayOf("Ljava/lang/Class;"), + "Ljava/lang/Object;", + ), + GetActiveNetworkInfo( + "Landroid/net/ConnectivityManager;", + "getActiveNetworkInfo", + arrayOf(), + "Landroid/net/NetworkInfo;", + ), + IsConnected( + "Landroid/net/NetworkInfo;", + "isConnected", + arrayOf(), + "Z", + ), + IsConnectedOrConnecting( + "Landroid/net/NetworkInfo;", + "isConnectedOrConnecting", + arrayOf(), + "Z", + ), + IsAvailable( + "Landroid/net/NetworkInfo;", + "isAvailable", + arrayOf(), + "Z", + ), + GetState( + "Landroid/net/NetworkInfo;", + "getState", + arrayOf(), + "Landroid/net/NetworkInfo\$State;", + ), + GetDetailedState( + "Landroid/net/NetworkInfo;", + "getDetailedState", + arrayOf(), + "Landroid/net/NetworkInfo\$DetailedState;", + ), + IsActiveNetworkMetered( + "Landroid/net/ConnectivityManager;", + "isActiveNetworkMetered", + arrayOf(), + "Z", + ), + GetActiveNetwork( + "Landroid/net/ConnectivityManager;", + "getActiveNetwork", + arrayOf(), + "Landroid/net/Network;", + ), + GetNetworkInfo( + "Landroid/net/ConnectivityManager;", + "getNetworkInfo", + arrayOf("Landroid/net/Network;"), + "Landroid/net/NetworkInfo;", + ), + HasTransport( + "Landroid/net/NetworkCapabilities;", + "hasTransport", + arrayOf("I"), + "Z", + ), + HasCapability( + "Landroid/net/NetworkCapabilities;", + "hasCapability", + arrayOf("I"), + "Z", + ), + RegisterBestMatchingNetworkCallback( + "Landroid/net/ConnectivityManager;", + "registerBestMatchingNetworkCallback", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + "V", + ), + RegisterDefaultNetworkCallback1( + "Landroid/net/ConnectivityManager;", + "registerDefaultNetworkCallback", + arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + "V", + ), + RegisterDefaultNetworkCallback2( + "Landroid/net/ConnectivityManager;", + "registerDefaultNetworkCallback", + arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + "V", + ), + RegisterNetworkCallback1( + "Landroid/net/ConnectivityManager;", + "registerNetworkCallback", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + "V", + ), + RegisterNetworkCallback2( + "Landroid/net/ConnectivityManager;", + "registerNetworkCallback", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"), + "V", + ), + RegisterNetworkCallback3( + "Landroid/net/ConnectivityManager;", + "registerNetworkCallback", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + "V", + ), + RequestNetwork1( + "Landroid/net/ConnectivityManager;", + "requestNetwork", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + "V", + ), + RequestNetwork2( + "Landroid/net/ConnectivityManager;", + "requestNetwork", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"), + "V", + ), + RequestNetwork3( + "Landroid/net/ConnectivityManager;", + "requestNetwork", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + "V", + ), + RequestNetwork4( + "Landroid/net/ConnectivityManager;", + "requestNetwork", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"), + "V", + ), + RequestNetwork5( + "Landroid/net/ConnectivityManager;", + "requestNetwork", + arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;", "I"), + "V", + ), + UnregisterNetworkCallback1( + "Landroid/net/ConnectivityManager;", + "unregisterNetworkCallback", + arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + "V", + ), + UnregisterNetworkCallback2( + "Landroid/net/ConnectivityManager;", + "unregisterNetworkCallback", + arrayOf("Landroid/app/PendingIntent;"), + "V", + ); + } + + override fun filterMap( + classDef: ClassDef, + method: Method, + instruction: Instruction, + instructionIndex: Int + ) = filterMapInstruction35c( + INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX, + classDef, + instruction, + instructionIndex + ) + + override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) { + val (methodType, instruction, instructionIndex) = entry + methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex) + } +} diff --git a/src/main/kotlin/app/revanced/patches/all/screenshot/removerestriction/patch/RemoveScreenshotRestrictionPatch.kt b/src/main/kotlin/app/revanced/patches/all/screenshot/removerestriction/patch/RemoveScreenshotRestrictionPatch.kt index 57fde50b6..b9f26c99b 100644 --- a/src/main/kotlin/app/revanced/patches/all/screenshot/removerestriction/patch/RemoveScreenshotRestrictionPatch.kt +++ b/src/main/kotlin/app/revanced/patches/all/screenshot/removerestriction/patch/RemoveScreenshotRestrictionPatch.kt @@ -1,106 +1,57 @@ package app.revanced.patches.all.screenshot.removerestriction.patch -import app.revanced.extensions.findMutableMethodOf import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Version -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.replaceInstruction -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultSuccess import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod -import org.jf.dexlib2.Opcode -import org.jf.dexlib2.iface.instruction.formats.Instruction35c -import org.jf.dexlib2.iface.reference.MethodReference +import app.revanced.util.patch.* +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.Instruction +import java.util.* @Patch(false) @Name("remove-screenshot-restriction") @Description("Removes the restriction of taking screenshots in apps that normally wouldn't allow it.") @Version("0.0.1") -class RemoveScreenshotRestrictionPatch : BytecodePatch() { +internal class RemoveScreenshotRestrictionPatch : AbstractTransformInstructionsPatch() { private companion object { - const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch;" + const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = + "Lapp/revanced/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch" + const val INTEGRATIONS_CLASS_DESCRIPTOR = "$INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX;" } // Information about method calls we want to replace - private enum class MethodCall( - val definedClassName: String, - val methodName: String, - val replacementMethodDefinition: String - ) { + enum class MethodCall( + override val definedClassName: String, + override val methodName: String, + override val methodParams: Array, + override val returnType: String + ): IMethodCall { SetFlags( "Landroid/view/Window;", "setFlags", - "setFlags(Landroid/view/Window;II)V", + arrayOf("I", "I"), + "V", ); - - fun replaceInstruction(method: MutableMethod, instruction: Instruction35c, instructionIndex: Int) { - when (this) { - SetFlags -> { - method.replaceInstruction( - instructionIndex, - "invoke-static { v${instruction.registerC}, v${instruction.registerD}, v${instruction.registerE} }, ${INTEGRATIONS_CLASS_DESCRIPTOR}->${replacementMethodDefinition}" - ) - } - } - } - - companion object { - fun fromMethodReference(methodReference: MethodReference) = values().firstOrNull { search -> - search.definedClassName == methodReference.definingClass && search.methodName == methodReference.name - } - } } - override fun execute(context: BytecodeContext): PatchResult { - // Find all instructions where one of the methods is called - buildMap { - context.classes.forEach { classDef -> - if (classDef.type == INTEGRATIONS_CLASS_DESCRIPTOR) { - // avoid infinite recursion - return@forEach - } + override fun filterMap( + classDef: ClassDef, + method: Method, + instruction: Instruction, + instructionIndex: Int + ) = filterMapInstruction35c( + INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX, + classDef, + instruction, + instructionIndex + ) - classDef.methods.let { methods -> - buildMap methodList@{ - methods.forEach methods@{ method -> - with(method.implementation?.instructions ?: return@methods) { - ArrayDeque>().also { patchIndices -> - this.forEachIndexed { index, instruction -> - if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@forEachIndexed - - val invokeInstruction = instruction as Instruction35c - val methodRef = invokeInstruction.reference as MethodReference - val methodCall = MethodCall.fromMethodReference(methodRef) ?: return@forEachIndexed - - patchIndices.add(Triple(methodCall, invokeInstruction, index)) - } - }.also { if (it.isEmpty()) return@methods }.let { patches -> - put(method, patches) - } - } - } - } - }.also { if (it.isEmpty()) return@forEach }.let { methodPatches -> - put(classDef, methodPatches) - } - } - }.forEach { (classDef, methods) -> - // And finally replace the instructions... - with(context.proxy(classDef).mutableClass) { - methods.forEach { (method, patches) -> - val mutableMethod = findMutableMethodOf(method) - while (!patches.isEmpty()) { - val (methodType, instruction, instructionIndex) = patches.removeLast() - methodType.replaceInstruction(mutableMethod, instruction, instructionIndex) - } - } - } - } - - return PatchResultSuccess() + override fun transform(mutableMethod: MutableMethod, entry: Instruction35cInfo) { + val (methodType, instruction, instructionIndex) = entry + methodType.replaceInvokeVirtualWithIntegrations(INTEGRATIONS_CLASS_DESCRIPTOR, mutableMethod, instruction, instructionIndex) } } diff --git a/src/main/kotlin/app/revanced/util/patch/AbstractTransformInstructionsPatch.kt b/src/main/kotlin/app/revanced/util/patch/AbstractTransformInstructionsPatch.kt new file mode 100644 index 000000000..3a1b8f23b --- /dev/null +++ b/src/main/kotlin/app/revanced/util/patch/AbstractTransformInstructionsPatch.kt @@ -0,0 +1,63 @@ +package app.revanced.util.patch + +import app.revanced.extensions.findMutableMethodOf +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.Method +import org.jf.dexlib2.iface.instruction.Instruction + +internal abstract class AbstractTransformInstructionsPatch : BytecodePatch() { + + abstract fun filterMap( + classDef: ClassDef, + method: Method, + instruction: Instruction, + instructionIndex: Int + ): T? + + abstract fun transform(mutableMethod: MutableMethod, entry: T) + + override fun execute(context: BytecodeContext): PatchResult { + // Find all instructions + buildMap { + context.classes.forEach { classDef -> + classDef.methods.let { methods -> + buildMap methodList@{ + methods.forEach methods@{ method -> + with(method.implementation?.instructions ?: return@methods) { + ArrayDeque().also { patchIndices -> + this.forEachIndexed { index, instruction -> + val result = filterMap(classDef, method, instruction, index) + if (result != null) { + patchIndices.add(result) + } + } + }.also { if (it.isEmpty()) return@methods }.let { patches -> + put(method, patches) + } + } + } + } + }.also { if (it.isEmpty()) return@forEach }.let { methodPatches -> + put(classDef, methodPatches) + } + } + }.forEach { (classDef, methods) -> + // And finally transform the instructions... + with(context.proxy(classDef).mutableClass) { + methods.forEach { (method, patches) -> + val mutableMethod = findMutableMethodOf(method) + while (!patches.isEmpty()) { + transform(mutableMethod, patches.removeLast()) + } + } + } + } + + return PatchResultSuccess() + } +} diff --git a/src/main/kotlin/app/revanced/util/patch/MethodCall.kt b/src/main/kotlin/app/revanced/util/patch/MethodCall.kt new file mode 100644 index 000000000..fe21f4fc1 --- /dev/null +++ b/src/main/kotlin/app/revanced/util/patch/MethodCall.kt @@ -0,0 +1,92 @@ +package app.revanced.util.patch + +import app.revanced.patcher.extensions.replaceInstruction +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.iface.ClassDef +import org.jf.dexlib2.iface.instruction.Instruction +import org.jf.dexlib2.iface.instruction.formats.Instruction35c +import org.jf.dexlib2.iface.reference.MethodReference + +internal typealias Instruction35cInfo = Triple + +internal interface IMethodCall { + val definedClassName: String + val methodName: String + val methodParams: Array + val returnType: String + + /** + * Replaces an invoke-virtual instruction with an invoke-static instruction, + * which calls a static replacement method in the respective integrations class. + * The method definition in the integrations class is expected to be the same, + * except that the method should be static and take as a first parameter + * an instance of the class, in which the original method was defined in. + * + * Example: + * + * original method: Window#setFlags(int, int) + * + * replacement method: Integrations#setFlags(Window, int, int) + */ + fun replaceInvokeVirtualWithIntegrations( + definingClassDescriptor: String, + method: MutableMethod, + instruction: Instruction35c, + instructionIndex: Int + ) { + val registers = arrayOf( + instruction.registerC, + instruction.registerD, + instruction.registerE, + instruction.registerF, + instruction.registerG + ) + val argsNum = methodParams.size + 1 // + 1 for instance of definedClassName + if (argsNum > registers.size) { + // should never happen, but just to be sure (also for the future) a safety check + throw RuntimeException( + "Not enough registers for ${definedClassName}#${methodName}: " + + "Required $argsNum registers, but only got ${registers.size}." + ) + } + + val args = registers.take(argsNum).joinToString(separator = ", ") { reg -> "v${reg}" } + val replacementMethodDefinition = + "${methodName}(${definedClassName}${methodParams.joinToString(separator = "")})${returnType}" + + method.replaceInstruction( + instructionIndex, + "invoke-static { $args }, ${definingClassDescriptor}->${replacementMethodDefinition}" + ) + } +} + +internal inline fun fromMethodReference(methodReference: MethodReference) + where E : Enum, E : IMethodCall = enumValues().firstOrNull { search -> + search.definedClassName == methodReference.definingClass + && search.methodName == methodReference.name + && methodReference.parameterTypes.toTypedArray().contentEquals(search.methodParams) +} + +internal inline fun filterMapInstruction35c( + integrationsClassDescriptorPrefix: String, + classDef: ClassDef, + instruction: Instruction, + instructionIndex: Int +): Instruction35cInfo? where E : Enum, E : IMethodCall { + if (classDef.type.startsWith(integrationsClassDescriptorPrefix)) { + // avoid infinite recursion + return null + } + + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) { + return null + } + + val invokeInstruction = instruction as Instruction35c + val methodRef = invokeInstruction.reference as MethodReference + val methodCall = fromMethodReference(methodRef) ?: return null + + return Instruction35cInfo(methodCall, invokeInstruction, instructionIndex) +}