From 42d2c277982ef63e6ad42d85e46f13c3ab50243c Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Thu, 22 May 2025 01:35:16 -0700 Subject: [PATCH] feat: Add `Disable pairip license check` patch (#4927) Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- patches/api/patches.api | 20 ++ .../patches/angulus/ads/RemoveAdsPatch.kt | 5 +- .../license/DisableLicenseCheckPatch.kt | 26 ++ .../misc/pairip/license/Fingerprints.kt | 17 ++ .../kotlin/app/revanced/util/BytecodeUtils.kt | 241 ++++++++++++++++-- .../main/kotlin/app/revanced/util/Utils.kt | 2 + 6 files changed, 282 insertions(+), 29 deletions(-) create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/Fingerprints.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index 6cca1abda..30ea1948e 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -652,6 +652,10 @@ public final class app/revanced/patches/shared/misc/mapping/ResourceMappingPatch public static final fun getResourceMappings ()Ljava/util/List; } +public final class app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatchKt { + public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; @@ -1615,8 +1619,24 @@ public final class app/revanced/util/BytecodeUtilsKt { public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V + public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V + public static synthetic fun returnLate$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V public static final fun traverseClassHierarchy (Lapp/revanced/patcher/patch/BytecodePatchContext;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V } diff --git a/patches/src/main/kotlin/app/revanced/patches/angulus/ads/RemoveAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/angulus/ads/RemoveAdsPatch.kt index 5eb585cf5..c1f3acb8e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/angulus/ads/RemoveAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/angulus/ads/RemoveAdsPatch.kt @@ -1,14 +1,17 @@ package app.revanced.patches.angulus.ads import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.misc.pairip.license.disableLicenseCheckPatch import app.revanced.util.returnEarly @Suppress("unused") val angulusPatch = bytecodePatch(name = "Hide ads") { compatibleWith("com.drinkplusplus.angulus") + dependsOn(disableLicenseCheckPatch) + execute { // Always return 0 as the daily measurement count. - getDailyMeasurementCountFingerprint.method.returnEarly() + getDailyMeasurementCountFingerprint.method.returnEarly(0) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatch.kt new file mode 100644 index 000000000..6284075e1 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/DisableLicenseCheckPatch.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.shared.misc.pairip.license + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly +import java.util.logging.Logger + +@Suppress("unused") +val disableLicenseCheckPatch = bytecodePatch( + name = "Disable pairip license check", + description = "Disable Play Integrity Protect (pairip) client-side license check." +) { + + execute { + if (processLicenseResponseFingerprint.methodOrNull == null || validateLicenseResponseFingerprint.methodOrNull == null) + return@execute Logger.getLogger(this::class.java.name) + .warning("Could not find pairip licensing check. No changes applied.") + + // Set first parameter (responseCode) to 0 (success status). + processLicenseResponseFingerprint.method.addInstruction(0, "const/4 p1, 0x0") + + // Short-circuit the license response validation. + validateLicenseResponseFingerprint.method.returnEarly(); + + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/Fingerprints.kt new file mode 100644 index 000000000..344fd02da --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/pairip/license/Fingerprints.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.shared.misc.pairip.license + +import app.revanced.patcher.fingerprint + +internal val processLicenseResponseFingerprint = fingerprint { + custom { method, classDef -> + classDef.type == "Lcom/pairip/licensecheck/LicenseClient;" && + method.name == "processResponse" + } +} + +internal val validateLicenseResponseFingerprint = fingerprint { + custom { method, classDef -> + classDef.type == "Lcom/pairip/licensecheck/ResponseValidator;" && + method.name == "validateResponse" + } +} diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 0ca022201..7e8c5ab21 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -133,6 +133,7 @@ internal val Instruction.registersUsed: List else -> listOf(registerC, registerD, registerE, registerF, registerG) } } + is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC) is TwoRegisterInstruction -> listOf(registerA, registerB) is OneRegisterInstruction -> listOf(registerA) @@ -170,7 +171,7 @@ internal val Instruction.isReturnInstruction: Boolean /** * Adds public [AccessFlags] and removes private and protected flags (if present). */ -internal fun Int.toPublicAccessFlags() : Int { +internal fun Int.toPublicAccessFlags(): Int { return this.or(AccessFlags.PUBLIC.value) .and(AccessFlags.PROTECTED.value.inv()) .and(AccessFlags.PRIVATE.value.inv()) @@ -489,9 +490,10 @@ fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int = indexOfFirstInst * @return The index of the first opcode specified, or -1 if not found. * @see indexOfFirstInstructionOrThrow */ -fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstruction(startIndex) { - opcode == targetOpcode -} +fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = + indexOfFirstInstruction(startIndex) { + opcode == targetOpcode + } /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. @@ -526,9 +528,10 @@ fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int = indexOfFi * @throws PatchException * @see indexOfFirstInstruction */ -fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(startIndex) { - opcode == targetOpcode -} +fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = + indexOfFirstInstructionOrThrow(startIndex) { + opcode == targetOpcode + } /** * Get the index of the first [Instruction] that matches the predicate, starting from [startIndex]. @@ -554,9 +557,10 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, filter: Instructi * @return -1 if the instruction is not found. * @see indexOfFirstInstructionReversedOrThrow */ -fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversed(startIndex) { - opcode == targetOpcode -} +fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = + indexOfFirstInstructionReversed(startIndex) { + opcode == targetOpcode + } /** * Get the index of matching instruction, @@ -593,9 +597,10 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF * @return The index of the instruction. * @see indexOfFirstInstructionReversed */ -fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow(startIndex) { - opcode == targetOpcode -} +fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int = + indexOfFirstInstructionReversedOrThrow(startIndex) { + opcode == targetOpcode + } /** * Get the index of matching instruction, @@ -652,7 +657,8 @@ fun Method.findInstructionIndicesReversedOrThrow(filter: Instruction.() -> Boole * _Returns an empty list if no indices are found_ * @see findInstructionIndicesReversedOrThrow */ -fun Method.findInstructionIndicesReversed(opcode: Opcode): List = findInstructionIndicesReversed { this.opcode == opcode } +fun Method.findInstructionIndicesReversed(opcode: Opcode): List = + findInstructionIndicesReversed { this.opcode == opcode } /** * @return An immutable list of indices of the opcode in reverse order. @@ -726,43 +732,222 @@ fun BytecodePatchContext.forEachLiteralValueInstruction( } } -/** - * Overrides the first instruction of a method with a constant return value. - * None of the method code will ever execute. - */ -fun MutableMethod.returnEarly(overrideValue: Boolean = false) = overrideReturnValue(overrideValue, false) +private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type" /** - * Overrides all return statements with a constant value. + * Overrides the first instruction of a method with a constant `Boolean` return value. + * None of the method code will ever execute. + * + * For methods that return an object or any array type, calling this method with `false` + * will force the method to return a `null` value. + */ +fun MutableMethod.returnEarly(value: Boolean = false) { + val returnType = returnType.first() + check(returnType == 'Z' || (!value && (returnType in setOf('V', 'L', '[')))) { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toHexString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Byte` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Byte) { + check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Short` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Short) { + check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Char` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Char) { + check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.code.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Int` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Int) { + check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Long` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Long) { + check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Float` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Float) { + check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides the first instruction of a method with a constant `Double` return value. + * None of the method code will ever execute. + */ +fun MutableMethod.returnEarly(value: Double) { + check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), false) +} + +/** + * Overrides all return statements with a constant `Boolean` value. + * All method code is executed the same as unpatched. + * + * For methods that return an object or any array type, calling this method with `false` + * will force the method to return a `null` value. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Boolean) { + val returnType = returnType.first() + if (returnType == 'V') { + error("Cannot return late for Method of void type") + } + check(returnType == 'Z' || (!value && returnType in setOf('L', '['))) { RETURN_TYPE_MISMATCH } + + overrideReturnValue(value.toHexString(), true) +} + +/** + * Overrides all return statements with a constant `Byte` value. * All method code is executed the same as unpatched. * * @see returnEarly */ -internal fun MutableMethod.returnLate(overrideValue: Boolean = false) = overrideReturnValue(overrideValue, true) +fun MutableMethod.returnLate(value: Byte) { + check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} -private fun MutableMethod.overrideReturnValue(bool: Boolean, returnLate: Boolean) { - val const = if (bool) "0x1" else "0x0" +/** + * Overrides all return statements with a constant `Short` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Short) { + check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} +/** + * Overrides all return statements with a constant `Char` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Char) { + check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.code.toString(), true) +} + +/** + * Overrides all return statements with a constant `Int` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Int) { + check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} + +/** + * Overrides all return statements with a constant `Long` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Long) { + check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} + +/** + * Overrides all return statements with a constant `Float` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Float) { + check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} + +/** + * Overrides all return statements with a constant `Double` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Double) { + check(returnType.first() == 'D') { RETURN_TYPE_MISMATCH } + overrideReturnValue(value.toString(), true) +} + +private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) { val instructions = when (returnType.first()) { - 'L' -> { + // If return type is an object, always return null. + 'L', '[' -> { """ - const/4 v0, $const + const/4 v0, 0x0 return-object v0 """ } 'V' -> { - if (returnLate) throw IllegalArgumentException("Cannot return late for method of void type") "return-void" } - 'I', 'Z' -> { + 'B', 'Z' -> { """ - const/4 v0, $const + const/4 v0, $value return v0 """ } + 'S', 'C' -> { + """ + const/16 v0, $value + return v0 + """ + } + + 'I', 'F' -> { + """ + const v0, $value + return v0 + """ + } + + 'J', 'D' -> { + """ + const-wide v0, $value + return-wide v0 + """ + } + else -> throw Exception("Return type is not supported: $this") } diff --git a/patches/src/main/kotlin/app/revanced/util/Utils.kt b/patches/src/main/kotlin/app/revanced/util/Utils.kt index 57f0edf03..ef7d0ef1a 100644 --- a/patches/src/main/kotlin/app/revanced/util/Utils.kt +++ b/patches/src/main/kotlin/app/revanced/util/Utils.kt @@ -6,3 +6,5 @@ internal object Utils { .joinToString("\n") { it.trimIndent() } // Remove the leading whitespace from each line. .trimIndent() // Remove the leading newline. } + +internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0" \ No newline at end of file