From cc5468fd5b38af8b820b8c944858f9e47996201b Mon Sep 17 00:00:00 2001 From: Juby210 <31005896+Juby210@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:04:18 +0200 Subject: [PATCH] backup module apk and settings along with app apk --- app/build.gradle.kts | 4 +- app/proguard-rules.pro | 3 + .../juby210/swiftbackupprem/BackupModule.kt | 48 +++++++ .../github/juby210/swiftbackupprem/Consts.kt | 3 + .../github/juby210/swiftbackupprem/DexKit.kt | 122 ++++++++++++++++++ .../juby210/swiftbackupprem/MainActivity.kt | 8 +- .../juby210/swiftbackupprem/Module.java | 73 ++--------- .../util/PreferencesManager.kt | 2 +- 8 files changed, 197 insertions(+), 66 deletions(-) create mode 100644 app/src/main/java/io/github/juby210/swiftbackupprem/BackupModule.kt create mode 100644 app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b1d911..4dc285d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "io.github.juby210.swiftbackupprem" minSdk = 27 targetSdk = 34 - versionCode = 202 - versionName = "2.0.2" + versionCode = 203 + versionName = "2.0.3" } buildTypes { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index c8053c4..5aad649 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -12,3 +12,6 @@ -allowaccessmodification -keep class io.github.juby210.swiftbackupprem.Module { *; } +-keep class io.github.juby210.swiftbackupprem.DexKit { + public final void findObfuscatedClasses(android.content.Context, java.lang.ClassLoader, java.lang.String); +} diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/BackupModule.kt b/app/src/main/java/io/github/juby210/swiftbackupprem/BackupModule.kt new file mode 100644 index 0000000..549eeee --- /dev/null +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/BackupModule.kt @@ -0,0 +1,48 @@ +package io.github.juby210.swiftbackupprem + +import android.content.Context +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedBridge +import io.github.juby210.swiftbackupprem.util.PreferencesManager +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.lang.reflect.Modifier + +fun hookBackupApk(cl: ClassLoader, ctx: Context, customFirebaseApp: Boolean, prefs: PreferencesManager) { + val pathsA = cl.loadClass("${paths!!.name}\$a") + XposedBridge.hookMethod(backupApk!!.getDeclaredMethod("c"), object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val pathsClass = paths!! + val aInstance = pathsClass.getDeclaredField("y").get(null) + val instance = pathsA.getDeclaredMethod("d").invoke(aInstance) + val basePath = pathsClass.getDeclaredMethod("m").invoke(instance) as String + + val dir = File(basePath, "sbp") + if (!dir.exists()) dir.mkdir() + + val apkFile = File(dir, "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}).apk") + if (!apkFile.exists()) + File(ctx.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, 0).applicationInfo.sourceDir).copyTo(apkFile, true) + + if (customFirebaseApp) with(prefs) { + val json = JSONObject().apply { + put("client", JSONArray().apply { + put(JSONObject().apply { + put("client_info", JSONObject().apply { put("mobilesdk_app_id", googleAppId) }) + put("api_key", JSONArray().apply { put(JSONObject().apply { put("current_key", googleApiKey) }) }) + }) + }) + put("project_info", JSONObject().apply { + put("firebase_url", firebaseDatabaseUrl) + put("project_number", gcmDefaultSenderId) + put("storage_bucket", googleStorageBucket) + put(Consts.projectId, projectId) + }) + put(Consts.oauthClientId, clientId) + }.toString() + File(dir, "google-services.json").run { if (!exists() || readText() != json) writeText(json) } + } + } + }) +} diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt b/app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt index 498c582..d85ef28 100644 --- a/app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt @@ -1,12 +1,15 @@ package io.github.juby210.swiftbackupprem object Consts { + const val packageName = "org.swiftapps.swiftbackup" + const val googleAppId = "google_app_id" const val googleApiKey = "google_api_key" const val firebaseDatabaseUrl = "firebase_database_url" const val gcmDefaultSenderId = "gcm_defaultSenderId" const val googleStorageBucket = "google_storage_bucket" const val projectId = "project_id" + const val oauthClientId = "oauth_client_id" @JvmStatic val classNames = mapOf(561 to "kf.s0", 569 to "rf.r0") diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt b/app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt new file mode 100644 index 0000000..8379cee --- /dev/null +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt @@ -0,0 +1,122 @@ +@file:JvmName("DexKit") + +package io.github.juby210.swiftbackupprem + +import android.content.Context +import android.util.Log +import android.widget.Toast +import org.luckypray.dexkit.DexKitBridge +import org.luckypray.dexkit.query.matchers.* +import java.lang.reflect.Modifier + +private val classesClientId = mapOf(561 to "kf.s0", 569 to "rf.r0") +private val classesBackupApk = mapOf(561 to "org.swiftapps.swiftbackup.common.w1", 569 to "org.swiftapps.swiftbackup.common.n2") +private val classesPaths = mapOf(561 to "me.b", 569 to "te.c") + +@JvmField +var clientId: Class<*>? = null +@JvmField +var backupApk: Class<*>? = null +@JvmField +var paths: Class<*>? = null + +@Suppress("DEPRECATION") +fun findObfuscatedClasses(ctx: Context, cl: ClassLoader, sourceDir: String) { + val ver = Integer.valueOf(ctx.packageManager.getPackageInfo(Consts.packageName, 0).versionCode) + if (classesClientId.containsKey(ver)) { + clientId = cl.loadClass(classesClientId[ver]) + backupApk = cl.loadClass(classesBackupApk[ver]) + paths = cl.loadClass(classesPaths[ver]) + } else { + System.loadLibrary("dexkit") + val excludePackages = listOf("android", "androidx", "com", "iammert", "java", "javax", "kotlin", "kotlinx", "moe", "nz.mega", + "okhttp3", "okio", "org", "retrofit", "rikka") + DexKitBridge.create(sourceDir)?.use { bridge -> + bridge.findClass { + excludePackages(excludePackages) + matcher { + fields { + add { + modifiers(Modifier.PUBLIC or Modifier.STATIC or Modifier.FINAL) + name("a") + } + add { + modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) + name("b") + } + add { + modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) + name("c") + type("java.lang.String") + } + add { + modifiers(Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL) + name("d") + type("android.net.Uri") + } + count(4) + } + addMethod { + modifiers(Modifier.PUBLIC or Modifier.FINAL) + returnType("android.content.Intent") + name("f") + addParamType("boolean") + } + } + }.firstOrNull()?.let { + clientId = it.getInstance(cl) + Log.d("SBP", "Found client id class: ${it.name}") + } + + bridge.findClass { + searchPackages("org.swiftapps.swiftbackup.common") + matcher { + fields { + addForName("a") + count(1) + } + addMethod { + modifiers(Modifier.PRIVATE or Modifier.FINAL) + returnType("void") + name("c") + paramCount(0) + usingStrings("stable", "swift_backup_apks/", "SwiftBackupApkSaver") + } + } + }.firstOrNull()?.let { + backupApk = it.getInstance(cl) + Log.d("SBP", "Found backup apk class: ${it.name}") + } + + bridge.findClass { + excludePackages(excludePackages) + matcher { + methods { + add { + name("") + addParamType("org.swiftapps.swiftbackup.anonymous.MFirebaseUser") + addParamType("java.lang.String") + paramCount(2) + usingStrings("accounts/", "backups/", "cache/", "apps/", "local/", "cloud/", "icon_cache/", "sms/", "calls/") + } + add { + modifiers(Modifier.PUBLIC or Modifier.FINAL) + returnType("java.lang.String") + name("m") + paramCount(0) + } + } + } + }.firstOrNull()?.let { + paths = it.getInstance(cl) + Log.d("SBP", "Found paths class: ${it.name}") + } + } + + if (clientId == null || backupApk == null || paths == null) Toast.makeText( + ctx, + "[SBP] Couldn't fully hook Swift Backup. Check if there's module update or report an issue.", + Toast.LENGTH_LONG + ).show() + } +} diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/MainActivity.kt b/app/src/main/java/io/github/juby210/swiftbackupprem/MainActivity.kt index b98fb11..96627b6 100644 --- a/app/src/main/java/io/github/juby210/swiftbackupprem/MainActivity.kt +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/MainActivity.kt @@ -1,5 +1,6 @@ package io.github.juby210.swiftbackupprem +import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.widget.Toast @@ -25,12 +26,14 @@ import org.json.JSONObject import kotlin.system.exitProcess class MainActivity : ComponentActivity() { + @SuppressLint("WorldReadableFiles") @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val prefs: PreferencesManager try { + @Suppress("DEPRECATION") prefs = PreferencesManager(getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", MODE_WORLD_READABLE)) } catch (e: Throwable) { Toast.makeText(this, "Enable module in LSPosed manager before using it", Toast.LENGTH_SHORT).show() @@ -105,8 +108,9 @@ class MainActivity : ComponentActivity() { firebaseDatabaseUrl = getString("firebase_url") gcmDefaultSenderId = getString("project_number") googleStorageBucket = getString("storage_bucket") - projectId = getString("project_id") + projectId = getString(Consts.projectId) } + if (json.has(Consts.oauthClientId)) clientId = json.getString(Consts.oauthClientId) } } catch (e: Throwable) { Toast.makeText(this@MainActivity, "Failed to parse json\n$e", Toast.LENGTH_LONG).show() @@ -161,7 +165,7 @@ class MainActivity : ComponentActivity() { } Button( - onClick = { clip.setText(AnnotatedString("org.swiftapps.swiftbackup")) }, + onClick = { clip.setText(AnnotatedString(Consts.packageName)) }, modifier = Modifier.padding(horizontal = 15.dp, vertical = 5.dp).fillMaxWidth().height(40.dp) ) { Text("Copy Swift Backup package name") diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/Module.java b/app/src/main/java/io/github/juby210/swiftbackupprem/Module.java index 171d933..a583498 100644 --- a/app/src/main/java/io/github/juby210/swiftbackupprem/Module.java +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/Module.java @@ -1,13 +1,7 @@ package io.github.juby210.swiftbackupprem; import android.content.Context; -import android.widget.Toast; -import org.luckypray.dexkit.DexKitBridge; -import org.luckypray.dexkit.query.FindClass; -import org.luckypray.dexkit.query.matchers.*; - -import java.lang.reflect.Modifier; import java.util.Arrays; import de.robv.android.xposed.*; @@ -16,7 +10,7 @@ public final class Module implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { - if (!lpparam.packageName.equals("org.swiftapps.swiftbackup")) return; + if (!lpparam.packageName.equals(Consts.packageName)) return; System.loadLibrary("nativelib"); var xPrefs = new XSharedPreferences(BuildConfig.APPLICATION_ID); @@ -35,6 +29,8 @@ public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Th XposedHelpers.findAndHookMethod(sa, "onCreate", new XC_MethodHook() { @SuppressWarnings("JavaReflectionInvocation") public void beforeHookedMethod(MethodHookParam param) throws Throwable { + var ctx = (Context) param.thisObject; + DexKit.findObfuscatedClasses(ctx, cl, lpparam.appInfo.sourceDir); var c = cl.loadClass("com.google.firebase.FirebaseApp"); if (customFirebaseApp) { var options = cl.loadClass("com.google.firebase.FirebaseOptions"); @@ -44,7 +40,7 @@ public void beforeHookedMethod(MethodHookParam param) throws Throwable { c.getDeclaredMethod("initializeApp", Context.class, options).invoke( null, - param.thisObject, + ctx, constructor.newInstance( prefs.getGoogleAppId(), prefs.getGoogleApiKey(), @@ -56,61 +52,16 @@ public void beforeHookedMethod(MethodHookParam param) throws Throwable { ) ); - var ctx = (Context) param.thisObject; - var ver = Integer.valueOf(ctx.getPackageManager().getPackageInfo(lpparam.packageName, 0).versionCode); - var classNames = Consts.getClassNames(); - Class classClientId; - if (classNames.containsKey(ver)) classClientId = cl.loadClass(classNames.get(ver)); - else { - System.loadLibrary("dexkit"); - try (DexKitBridge bridge = DexKitBridge.create(lpparam.appInfo.sourceDir)) { - var classData = bridge.findClass( - FindClass.create() - .excludePackages("android", "androidx", "com", "iammert", "java", "javax", "kotlin", "kotlinx", "moe", "nz.mega", - "okhttp3", "okio", "org", "retrofit", "rikka") - .matcher( - ClassMatcher.create() - .fields( - FieldsMatcher.create() - .add(FieldMatcher.create().modifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL).name("a")) - .add(FieldMatcher.create().modifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL).name("b")) - .add(FieldMatcher.create().modifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL).name("c") - .type("java.lang.String")) - .add(FieldMatcher.create().modifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL).name("d") - .type("android.net.Uri")) - .count(4) - ) - .addMethod( - MethodMatcher.create() - .modifiers(Modifier.PUBLIC | Modifier.FINAL) - .returnType("android.content.Intent") - .name("f") - .addParamType("boolean") - ) - ) - ).firstOrNull(); - - if (classData == null) { - Toast.makeText( - ctx, - "[SBP] Couldn't fully hook Swift Backup. Check if there's module update or report an issue.", - Toast.LENGTH_LONG - ).show(); - classClientId = null; - } else classClientId = classData.getInstance(cl); + if (DexKit.clientId != null) XposedBridge.hookMethod(DexKit.clientId.getDeclaredMethod("f", boolean.class), new XC_MethodHook() { + public void beforeHookedMethod(MethodHookParam param) throws Throwable { + var clientId = DexKit.clientId.getDeclaredField("c"); + clientId.setAccessible(true); + clientId.set(null, prefs.getClientId()); } - } - - if (classClientId != null) { - XposedBridge.hookMethod(classClientId.getDeclaredMethod("f", boolean.class), new XC_MethodHook() { - public void beforeHookedMethod(MethodHookParam param) throws Throwable { - var clientId = classClientId.getDeclaredField("c"); - clientId.setAccessible(true); - clientId.set(null, prefs.getClientId()); - } - }); - } + }); } else c.getDeclaredMethod("initializeApp", Context.class).invoke(null, param.thisObject); + + if (DexKit.backupApk != null && DexKit.paths != null) BackupModuleKt.hookBackupApk(cl, ctx, customFirebaseApp, prefs); } }); diff --git a/app/src/main/java/io/github/juby210/swiftbackupprem/util/PreferencesManager.kt b/app/src/main/java/io/github/juby210/swiftbackupprem/util/PreferencesManager.kt index 186b447..16d8f62 100644 --- a/app/src/main/java/io/github/juby210/swiftbackupprem/util/PreferencesManager.kt +++ b/app/src/main/java/io/github/juby210/swiftbackupprem/util/PreferencesManager.kt @@ -54,7 +54,7 @@ class PreferencesManager(private val prefs: SharedPreferences) { var gcmDefaultSenderId by stringPreference(Consts.gcmDefaultSenderId) var googleStorageBucket by stringPreference(Consts.googleStorageBucket) var projectId by stringPreference(Consts.projectId) - var clientId by stringPreference("oauth_client_id") + var clientId by stringPreference(Consts.oauthClientId) var customFirebaseApp by booleanPreference("custom_firebase_app") }