diff --git a/app/src/main/java/dev/jkcarino/revanced/integrations/gboard/SignatureHookApp.kt b/app/src/main/java/dev/jkcarino/revanced/integrations/gboard/SignatureHookApp.kt new file mode 100644 index 0000000000..a009ef0d70 --- /dev/null +++ b/app/src/main/java/dev/jkcarino/revanced/integrations/gboard/SignatureHookApp.kt @@ -0,0 +1,16 @@ +package dev.jkcarino.revanced.integrations.gboard + +import android.content.Context +import com.google.android.apps.inputmethod.latin.ImeLatinApp +import dev.jkcarino.revanced.integrations.shared.PmsHookApplication + +class SignatureHookApp : ImeLatinApp() { + + override fun attachBaseContext(base: Context) { + val pmsHookApplication = PmsHookApplication( + signature = "AQAABEcwggRDMIIDK6ADAgECAgkAwuCHRmRKMI0wDQYJKoZIhvcNAQEEBQAwdDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC0dvb2dsZSBJbmMuMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQDEwdBbmRyb2lkMB4XDTA4MDgyMTIzMTMzNFoXDTM2MDEwNzIzMTMzNFowdDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC0dvb2dsZSBJbmMuMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQDEwdBbmRyb2lkMIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAq1YuANg7ogiuCpZvEk4p2hHyq1bQj1jizKkTA+m3VNNy9kCnGx3LEwlnYk5GVqd3apIZPbLlv7ckqR53GIsOakekOzPZYJt3GDFFzN97LlhmdMnhVlsfTGpZVb/yUaY9q/nFXCciIlLodeT4FUpkX4lxaMCxv8YS6r94V2m7NKp5hNx+LqJ2TK6DB9jBcVTX7l9kpRpEpgLCSQVBV9wCzV9cDlX774UZ++Mn8LFRFpLFoG8Z0YOF9cTbwta5P2jMKXnHDhirk4ZrO9XbiZlVKg47TJnfWPuRi+3Bgro14APBtLEN0kSo7iT//TM4cqtSIZhe2rD8DQsUW2qhkoWOeQIBA6OB2TCB1jAdBgNVHQ4EFgQUx32MwiEXViWaf9OC32vjmOTXhqUwgaYGA1UdIwSBnjCBm4AUx32MwiEXViWaf9OC32vjmOTXhqWheKR2MHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZIIJAMLgh0ZkSjCNMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAG3SUs7vhTAsNgqqzpObz/LMqQS7XXoWYfiuRrKZQgTQ/0pox+0aUx7EWVpiPOYHY7FnKXp641cSxAfyCPDLEJQpEk17EGIZwITKPrP5rV+4ce+SJpqL4ovxbUTI2aCObLLwBbs/4suWRH6GjnMQdq1Fsz9gCeoZwWHmJkGqmScd/VIoxcWHh13bf0UnWNZh9swMzLc1LkJMxDZcUjUy9zJRN1k8SuNB9NtB7doNCxBxp8RA8P6eoBy2J8pnQ2nQhL0v2RH/Bs2/LPoQ3A+JOuNXYpGQSMfvxkxxRBeDQvcFgcneVzr1WzkN1/25QYYxiV1fdZ8wESaH/2IUEMBpMIo=" + ) + pmsHookApplication.hook(base) + super.attachBaseContext(base) + } +} diff --git a/app/src/main/java/dev/jkcarino/revanced/integrations/shared/PmsHookApplication.kt b/app/src/main/java/dev/jkcarino/revanced/integrations/shared/PmsHookApplication.kt new file mode 100644 index 0000000000..75d42bb00a --- /dev/null +++ b/app/src/main/java/dev/jkcarino/revanced/integrations/shared/PmsHookApplication.kt @@ -0,0 +1,114 @@ +package dev.jkcarino.revanced.integrations.shared + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.Signature +import android.util.Base64 +import java.io.ByteArrayInputStream +import java.io.DataInputStream +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy + +/** + * A custom [Application] class that hooks into the Android's [PackageManager] to modify the + * package information returned for the app. This is used to override the package signatures, + * allowing us to bypass signature verification checks. + * + * Source: https://github.com/L-JINBIN/ApkSignatureKiller/blob/master/hook/cc/binmt/signature/PmsHookApplication.java + */ +internal class PmsHookApplication( + private val signature: String +) { + /** + * This hooks into the [PackageManager] and replaces the original package manager with a + * proxy object that modifies the package information returned for the app. + */ + @SuppressLint("PrivateApi", "DiscouragedPrivateApi") + fun hook(context: Context) { + try { + val byteArray = Base64.decode(signature, Base64.DEFAULT) + val signatures = ByteArrayInputStream(byteArray).use { inputStream -> + DataInputStream(inputStream).use { dataInputStream -> + val signatureCount = dataInputStream.read() and 0xFF + Array(signatureCount) { + ByteArray(dataInputStream.readInt()).also { + dataInputStream.readFully(it) + } + } + } + } + + // Get the global ActivityThread object + val activityThreadClass = Class.forName("android.app.ActivityThread") + val currentActivityThreadMethod = + activityThreadClass.getDeclaredMethod("currentActivityThread") + val currentActivityThread = currentActivityThreadMethod.invoke(null) + + // Get the original sPackageManager from ActivityThread + val sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager") + sPackageManagerField.isAccessible = true + val sPackageManager = sPackageManagerField.get(currentActivityThread) + + // Prepare a proxy object to replace the original one + val iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager") + val proxy = Proxy.newProxyInstance( + iPackageManagerInterface.classLoader, + arrayOf>(iPackageManagerInterface), + PmsHookInvocationHandler( + packageManager = sPackageManager, + signatures = signatures, + appPackageName = context.packageName + ) + ) + + // Replace the sPackageManager field in ActivityThread + sPackageManagerField.set(currentActivityThread, proxy) + + // Replace mPM object in ApplicationPackageManager + val pm = context.packageManager + val mPmField = pm.javaClass.getDeclaredField("mPM") + mPmField.isAccessible = true + mPmField.set(pm, proxy) + + println("PmsHook success.") + } catch (error: Exception) { + System.err.println("PmsHook failed.") + error.printStackTrace() + } + } +} + +/** + * This class is used as part of the package manager hooking mechanism implemented in the + * [PmsHookApplication] class. + */ +private class PmsHookInvocationHandler( + private val packageManager: Any?, + private val signatures: Array, + private val appPackageName: String +) : InvocationHandler { + + /** + * This intercepts calls to the [getPackageInfo] method of the package manager and + * modifies the returned [PackageInfo] object to use the new package signatures. + */ + override fun invoke(proxy: Any, method: Method, args: Array?): Any? { + if ("getPackageInfo" == method.name) { + val packageName = args!![0].toString() + val flag = args[1].toString().toInt() + if (flag and GET_SIGNATURES != 0 && appPackageName == packageName) { + val packageInfo = method.invoke(packageManager, *args) as PackageInfo + packageInfo.signatures = Array(signatures.size) { Signature(signatures[it]) } + return packageInfo + } + } + return method.invoke(packageManager, *args.orEmpty()) + } + + companion object { + private const val GET_SIGNATURES = 0x00000040 + } +} diff --git a/stub/src/main/java/com/google/android/apps/inputmethod/latin/ImeLatinApp.java b/stub/src/main/java/com/google/android/apps/inputmethod/latin/ImeLatinApp.java new file mode 100644 index 0000000000..8bce1297f9 --- /dev/null +++ b/stub/src/main/java/com/google/android/apps/inputmethod/latin/ImeLatinApp.java @@ -0,0 +1,6 @@ +package com.google.android.apps.inputmethod.latin; + +import android.app.Application; + +public class ImeLatinApp extends Application { +}