From 60a02aa90a46a6b1a0f8dfb434643dc7b84549c2 Mon Sep 17 00:00:00 2001 From: naijun0403 Date: Mon, 19 May 2025 20:23:04 +0900 Subject: [PATCH] feat(kakaotalk): add bypass moat check patch and associated fingerprints It's up, but it's not working, need to check further --- patches/api/patches.api | 4 + .../integrity/BypassMoatCheckPatch.kt | 80 +++++++++++++++++++ .../BypassMoatCheckFingerprint.kt | 65 +++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/BypassMoatCheckPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/fingerprints/BypassMoatCheckFingerprint.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index ce378469b..8771e8071 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -269,6 +269,10 @@ public final class app/revanced/patches/kakaotalk/ghost/GhostModePatchKt { public static final fun getGhostMode ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/kakaotalk/integrity/BypassMoatCheckPatchKt { + public static final fun getBypassMoatCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/kakaotalk/integrity/BypassRequestChecksumsPatchKt { public static final fun getBypassRequestChecksumPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/BypassMoatCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/BypassMoatCheckPatch.kt new file mode 100644 index 000000000..c5e35e46d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/BypassMoatCheckPatch.kt @@ -0,0 +1,80 @@ +package app.revanced.patches.kakaotalk.integrity + +import app.revanced.patcher.Fingerprint +import app.revanced.patcher.extensions.InstructionExtensions.instructions +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.kakaotalk.integrity.fingerprints.bypassMoatCheckFingerprintOne +import app.revanced.patches.kakaotalk.integrity.fingerprints.bypassMoatCheckFingerprintTwo +import app.revanced.patches.kakaotalk.integrity.fingerprints.postprocessMoatCheckFailedFingerprint +import app.revanced.util.getReference +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c +import com.android.tools.smali.dexlib2.iface.instruction.Instruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableFieldReference + +@Suppress("unused") +val bypassMoatCheckPatch = bytecodePatch( + name = "Bypass Moat check", + description = "Bypass Moat check that prevents the app from running.", +) { + compatibleWith("com.kakao.talk"("25.4.2")) + + execute { + val patch: (Fingerprint) -> Unit = { + val method = it.method + val insns = method.instructions + + insns + .filterIsInstance() + .filter { inst -> + inst.opcode == Opcode.SGET_OBJECT && + inst.reference == ImmutableFieldReference( + "Ljava/lang/Boolean;", "FALSE", "Ljava/lang/Boolean;" + ) + } + .forEach { inst -> + println("Replacing ${inst.reference} with TRUE") + val idx = insns.indexOf(inst) + method.replaceInstruction( + idx, + BuilderInstruction21c( + Opcode.SGET_OBJECT, + inst.registerA, + ImmutableFieldReference( + "Ljava/lang/Boolean;", "TRUE", "Ljava/lang/Boolean;" + ) + ) + ) + } + + val postprocessMoatCheckFailedMethod = postprocessMoatCheckFailedFingerprint.method + + val toRemove = mutableListOf() + insns.forEachIndexed { i, inst -> + if (inst is BuilderInstruction35c && + inst.opcode == Opcode.INVOKE_VIRTUAL && + (inst.getReference()?.name == postprocessMoatCheckFailedMethod.name) && + inst.getReference()?.definingClass == + "Lcom/kakaopay/shared/security/moat/PaySecurityWorker;" + ) { + for (j in 0..3) { + insns.getOrNull(i + j)?.let { toRemove += it } + } + } + } + toRemove + .distinct() + .sortedByDescending { insns.indexOf(it) } + .forEach { inst -> + method.removeInstruction(insns.indexOf(inst)) + } + } + + patch(bypassMoatCheckFingerprintOne) + patch(bypassMoatCheckFingerprintTwo) + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/fingerprints/BypassMoatCheckFingerprint.kt b/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/fingerprints/BypassMoatCheckFingerprint.kt new file mode 100644 index 000000000..7692cd653 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/kakaotalk/integrity/fingerprints/BypassMoatCheckFingerprint.kt @@ -0,0 +1,65 @@ +package app.revanced.patches.kakaotalk.integrity.fingerprints + +import app.revanced.patcher.fingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal val bypassMoatCheckFingerprintOne = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameters("Ljava/lang/Object;", "Ljava/lang/Object;", "Ljava/lang/Object;") + returns("Ljava/lang/Object;") + strings("detectResult", "", "") + opcodes( + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.SGET_OBJECT, + Opcode.SGET_OBJECT, + Opcode.CONST_4, + Opcode.IF_NE, + Opcode.CHECK_CAST, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + ) +} + +internal val bypassMoatCheckFingerprintTwo = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameters("Ljava/lang/Object;", "Ljava/lang/Object;", "Ljava/lang/Object;") + returns("Ljava/lang/Object;") + strings("detectResult", "", "") + opcodes( + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.CONST_STRING, + Opcode.INVOKE_STATIC, + Opcode.CHECK_CAST, + Opcode.NEW_INSTANCE, + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.SGET_OBJECT, + Opcode.IF_EQZ, + ) +} + +internal val postprocessMoatCheckFailedFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + parameters("Lcom/kakaopay/kpsd/moat/sdk/MoatFlag;", "Ljava/lang/String;", "[Ljava/lang/String;") + strings("msg_title", "msg_body", "OUTPUT_KEY_FAILURE_TITLE", "OUTPUT_KEY_FAILURE_REASON", "ADS_BLOCK은 result message를 사용해야 합니다.", "let(...)", "OUTPUT_KEY_FAILURE_TYPE", "OUTPUT_KEY_PACKAGE_NAMES") +} \ No newline at end of file