Skip to content

Commit

Permalink
backup module apk and settings along with app apk
Browse files Browse the repository at this point in the history
  • Loading branch information
Juby210 committed Oct 28, 2023
1 parent 2af98db commit cc5468f
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 66 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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) }
}
}
})
}
3 changes: 3 additions & 0 deletions app/src/main/java/io/github/juby210/swiftbackupprem/Consts.kt
Original file line number Diff line number Diff line change
@@ -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")
Expand Down
122 changes: 122 additions & 0 deletions app/src/main/java/io/github/juby210/swiftbackupprem/DexKit.kt
Original file line number Diff line number Diff line change
@@ -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("<init>")
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()
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand Down
73 changes: 12 additions & 61 deletions app/src/main/java/io/github/juby210/swiftbackupprem/Module.java
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -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);
Expand All @@ -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");
Expand All @@ -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(),
Expand All @@ -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);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

0 comments on commit cc5468f

Please sign in to comment.