diff --git a/.gitignore b/.gitignore index 83911abb4..dcc31361b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ out *.apk /config.prop /update.sh +/dict.txt # Built binaries native/out diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8ae2aab1d..742804a21 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,4 @@ import org.apache.tools.ant.filters.FixCrLfFilter -import java.io.PrintStream plugins { id("com.android.application") @@ -143,32 +142,11 @@ android.applicationVariants.all { val outSrcDir = File(buildDir, "generated/source/keydata/$name") val outSrc = File(outSrcDir, "com/topjohnwu/magisk/signing/KeyData.java") - fun PrintStream.newField(name: String, file: File) { - println("public static byte[] $name() {") - print("byte[] buf = {") - val bytes = file.readBytes() - print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" }) - println("};") - println("return buf;") - println("}") - } - val genSrcTask = tasks.register("generate${name.capitalize()}KeyData") { inputs.dir(keysDir) outputs.file(outSrc) doLast { - outSrc.parentFile.mkdirs() - PrintStream(outSrc).use { - it.println("package com.topjohnwu.magisk.signing;") - it.println("public final class KeyData {") - - it.newField("testCert", File(keysDir, "testkey.x509.pem")) - it.newField("testKey", File(keysDir, "testkey.pk8")) - it.newField("verityCert", File(keysDir, "verity.x509.pem")) - it.newField("verityKey", File(keysDir, "verity.pk8")) - - it.println("}") - } + genKeyData(keysDir, outSrc) } } registerJavaGeneratingTask(genSrcTask, outSrcDir) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index df3605203..ac99c2102 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -49,6 +49,10 @@ -repackageclasses 'a' -allowaccessmodification +-obfuscationdictionary ../dict.txt +-classobfuscationdictionary ../dict.txt +-packageobfuscationdictionary ../dict.txt + -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider diff --git a/app/shared/build.gradle.kts b/app/shared/build.gradle.kts index 7f90192bd..ad8a82bc1 100644 --- a/app/shared/build.gradle.kts +++ b/app/shared/build.gradle.kts @@ -8,3 +8,7 @@ android { consumerProguardFiles("proguard-rules.pro") } } + +dependencies { + api("io.michaelrocks:paranoid-core:0.3.5") +} diff --git a/app/shared/src/main/AndroidManifest.xml b/app/shared/src/main/AndroidManifest.xml index 5423da11b..220750909 100644 --- a/app/shared/src/main/AndroidManifest.xml +++ b/app/shared/src/main/AndroidManifest.xml @@ -24,4 +24,9 @@ android:usesCleartextTraffic="true" tools:ignore="UnusedAttribute" /> + + + diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java b/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java index c5daa0398..93a623a37 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java @@ -1,5 +1,7 @@ package com.topjohnwu.magisk; +import static android.os.Build.VERSION.SDK_INT; + import android.content.Context; import android.content.res.AssetManager; @@ -7,8 +9,9 @@ import java.io.File; import java.lang.reflect.Method; import java.util.Map; -import static android.os.Build.VERSION.SDK_INT; +import io.michaelrocks.paranoid.Obfuscate; +@Obfuscate public class DynAPK { // Indices of the object array diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java b/app/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java index f4261dd95..bafe479ad 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java @@ -19,9 +19,12 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import io.michaelrocks.paranoid.Obfuscate; + /** * Modified from androidx.core.content.FileProvider */ +@Obfuscate public class FileProvider extends ContentProvider { private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}; diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/ProviderInstaller.java b/app/shared/src/main/java/com/topjohnwu/magisk/ProviderInstaller.java index 56ed00232..c969a85ba 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/ProviderInstaller.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/ProviderInstaller.java @@ -2,6 +2,9 @@ package com.topjohnwu.magisk; import android.content.Context; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class ProviderInstaller { public static boolean install(Context context) { diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index 22a87e402..d76b3a7e8 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -12,6 +12,9 @@ import com.topjohnwu.magisk.FileProvider; import java.io.File; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class APKInstall { public static Intent installIntent(Context c, File apk) { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8eb5b28e0..0d621bda2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -71,11 +71,6 @@ android:exported="false" android:grantUriPermissions="true" /> - - - { override fun apply(project: Project) = project.applyPlugin() private fun Project.applyPlugin() { + initRandom(rootProject.file("dict.txt")) props.clear() rootProject.file("gradle.properties").inputStream().use { props.load(it) } val configPath: String? by this diff --git a/buildSrc/src/main/java/Codegen.kt b/buildSrc/src/main/java/Codegen.kt new file mode 100644 index 000000000..d56043acf --- /dev/null +++ b/buildSrc/src/main/java/Codegen.kt @@ -0,0 +1,286 @@ + +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.PrintStream +import java.security.SecureRandom +import java.util.* +import java.util.zip.GZIPOutputStream +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +// Set non-zero value here to fix the random seed to create reproducible builds +const val RAND_SEED = 0 +private lateinit var RANDOM: Random + +private val c1 = mutableListOf() +private val c2 = mutableListOf() +private val c3 = mutableListOf() + +fun initRandom(dict: File) { + RANDOM = if (RAND_SEED != 0) Random(RAND_SEED.toLong()) else SecureRandom() + c1.clear() + c2.clear() + c3.clear() + for (a in chain('a'..'z', 'A'..'Z')) { + if (a != 'a' && a != 'A') { + c1.add("$a") + } + for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) { + c2.add("$a$b") + for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) { + c3.add("$a$b$c") + } + } + } + c1.shuffle(RANDOM) + c2.shuffle(RANDOM) + c3.shuffle(RANDOM) + PrintStream(dict).use { + for (c in chain(c1, c2, c3)) { + it.println(c) + } + } +} + +private fun chain(vararg iters: Iterable) = sequence { + iters.forEach { it.forEach { v -> yield(v) } } +} + +private fun PrintStream.byteField(name: String, bytes: ByteArray) { + println("public static byte[] $name() {") + print("byte[] buf = {") + print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" }) + println("};") + println("return buf;") + println("}") +} + +fun genKeyData(keysDir: File, outSrc: File) { + outSrc.parentFile.mkdirs() + PrintStream(outSrc).use { + it.println("package com.topjohnwu.magisk.signing;") + it.println("public final class KeyData {") + + it.byteField("testCert", File(keysDir, "testkey.x509.pem").readBytes()) + it.byteField("testKey", File(keysDir, "testkey.pk8").readBytes()) + it.byteField("verityCert", File(keysDir, "verity.x509.pem").readBytes()) + it.byteField("verityKey", File(keysDir, "verity.pk8").readBytes()) + + it.println("}") + } +} + +fun genStubManifest(srcDir: File, outDir: File): String { + class Component( + val real: String, + val stub: String, + val xml: String + ) + + outDir.deleteRecursively() + + val mainPkgDir = File(outDir, "com/topjohnwu/magisk") + mainPkgDir.mkdirs() + + fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level)) + + val cmpList = mutableListOf() + + cmpList.add(Component( + "androidx.core.app.CoreComponentFactory", + "DelegateComponentFactory", + "" + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.core.App", + "DelegateApplication", + "" + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.core.Provider", + "FileProvider", + """ + |""".ind(2) + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.core.Receiver", + "dummy.DummyReceiver", + """ + | + | + | + | + | + | + | + | + | + | + | + |""".ind(2) + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.core.SplashActivity", + "DownloadActivity", + """ + | + | + | + | + | + |""".ind(2) + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.ui.MainActivity", + "", + """|""".ind(2) + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.ui.surequest.SuRequestActivity", + "", + """ + | + | + | + | + | + |""".ind(2) + )) + + cmpList.add(Component( + "com.topjohnwu.magisk.core.download.DownloadService", + "", + """|""".ind(2) + )) + + cmpList.add(Component( + "androidx.work.impl.background.systemjob.SystemJobService", + "", + """ + |""".ind(2) + )) + + val names = mutableListOf() + names.addAll(c1) + names.addAll(c2.subList(0, 10)) + names.addAll(c3.subList(0, 10)) + names.shuffle(RANDOM) + + var idx = 0 + fun genCmpName(): String { + val name = "${names[idx++]}.${names[idx++]}" + return name[0].toLowerCase() + name.substring(1) + } + + fun genClass(clzName: String, type: String) { + val (pkg, name) = clzName.split('.') + PrintStream(File(mainPkgDir, "$name.java")).use { + it.println("package $pkg;") + it.println("public class $name extends com.topjohnwu.magisk.$type {}") + } + } + + val cmps = mutableListOf() + val usedNames = mutableListOf() + val maps = StringBuilder() + + for (gen in cmpList) { + val name = genCmpName() + usedNames.add(name) + maps.append("|map.put(\"$name\", \"${gen.real}\");".ind(2)) + maps.append('\n') + if (gen.stub.isNotEmpty()) { + if (gen.stub != "DelegateComponentFactory") { + maps.append("|internalMap.put(\"$name\", com.topjohnwu.magisk.${gen.stub}.class);".ind(2)) + maps.append('\n') + } + if (gen.stub.startsWith("Delegate")) { + genClass(name, gen.stub) + } + } + if (gen.xml.isNotEmpty()) { + cmps.add(gen.xml.format(name)) + } + } + + // Shuffle the order of the components + cmps.shuffle(RANDOM) + val xml = File(srcDir, "AndroidManifest.xml").readText() + val genXml = xml.format(usedNames[0], usedNames[1], cmps.joinToString("\n\n")) + + // Write mapping information to code + val mapping = File(srcDir, "Mapping.java").readText().format(maps) + PrintStream(File(mainPkgDir, "Mapping.java")).use { + it.print(mapping) + } + + return genXml +} + +fun genEncryptedResources(res: File, outDir: File) { + val mainPkgDir = File(outDir, "com/topjohnwu/magisk") + // Rename R.java + val r = File(mainPkgDir, "R.java").let { + val txt = it.readText() + it.delete() + txt + } + File(mainPkgDir, "A.java").writeText(r.replace("class R", "class A")) + + // Generate iv and key + val iv = ByteArray(16) + val key = ByteArray(32) + RANDOM.nextBytes(iv) + RANDOM.nextBytes(key) + + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) + val bos = ByteArrayOutputStream() + + FileInputStream(res).use { + // First compress, then encrypt + GZIPOutputStream(CipherOutputStream(bos, cipher)).use { os -> + it.transferTo(os) + } + } + + PrintStream(File(mainPkgDir, "Bytes.java")).use { + it.println("package com.topjohnwu.magisk;") + it.println("public final class Bytes {") + + it.byteField("key", key) + it.byteField("iv", iv) + it.byteField("res", bos.toByteArray()) + + it.println("}") + } +} diff --git a/stub/.gitignore b/stub/.gitignore index 796b96d1c..fa72e45ca 100644 --- a/stub/.gitignore +++ b/stub/.gitignore @@ -1 +1,2 @@ /build +/src/main/AndroidManifest.xml diff --git a/stub/build.gradle.kts b/stub/build.gradle.kts index fadb91d3f..4e0137b5f 100644 --- a/stub/build.gradle.kts +++ b/stub/build.gradle.kts @@ -1,7 +1,30 @@ + +import io.michaelrocks.paranoid.plugin.ParanoidExtension +import org.gradle.internal.os.OperatingSystem +import java.io.OutputStream +import java.io.PrintStream +import java.nio.file.Paths + plugins { id("com.android.application") } +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("io.michaelrocks:paranoid-gradle-plugin:0.3.5") + } +} + +apply(plugin = "io.michaelrocks.paranoid") + +extensions.configure("paranoid") { + obfuscationSeed = if (RAND_SEED != 0) RAND_SEED else null + includeSubprojects = true +} + android { val canary = !Config.version.contains(".") @@ -11,7 +34,7 @@ android { defaultConfig { applicationId = "com.topjohnwu.magisk" versionCode = 1 - versionName = Config.version + versionName = "1.0" buildConfigField("int", "STUB_VERSION", Config.stubVersion) buildConfigField("String", "APK_URL", url?.let { "\"$it\"" } ?: "null" ) } @@ -24,16 +47,98 @@ android { } } - androidResources { - additionalParameters("--package-id", "0x80") - } - dependenciesInfo { includeInApk = false includeInBundle = false } } +// Make sure we have a working manifest while building +val ensureManifest by tasks.registering { + val manifest = file("src/main/AndroidManifest.xml") + if (!manifest.exists()) { + PrintStream(manifest).use { + it.println("") + } + } +} + +tasks["preBuild"]?.dependsOn(ensureManifest) + +android.applicationVariants.all { + val manifest = file("src/main/AndroidManifest.xml") + val outSrcDir = File(buildDir, "generated/source/obfuscate/$name") + val templateDir = file("template") + val resDir = file("res") + + val androidJar = Paths.get(android.sdkDirectory.path, "platforms", + android.compileSdkVersion, "android.jar") + + val aaptCommand = if (OperatingSystem.current().isWindows) "aapt2.exe" else "aapt2" + val aapt = Paths.get(android.sdkDirectory.path, + "build-tools", android.buildToolsVersion, aaptCommand) + + val dummy = object : OutputStream() { + override fun write(b: Int) {} + override fun write(bytes: ByteArray, off: Int, len: Int) {} + } + + val genSrcTask = tasks.register("generate${name.capitalize()}ObfuscatedSources") { + doLast { + val xml = genStubManifest(templateDir, outSrcDir) + PrintStream(manifest).use { + it.print(xml) + } + + val compileTmp = File.createTempFile("tmp", ".zip") + val linkTmp = File.createTempFile("tmp", ".zip") + val optTmp = File.createTempFile("tmp", ".zip") + val stubXml = File.createTempFile("tmp", ".xml") + try { + PrintStream(stubXml).use { + it.println("") + } + + exec { + commandLine(aapt, "compile", + "-o", compileTmp, + "--dir", resDir) + standardOutput = dummy + errorOutput = dummy + } + + exec { + commandLine(aapt, "link", + "-o", linkTmp, + "-I", androidJar, + "--min-sdk-version", android.defaultConfig.minSdk, + "--target-sdk-version", android.defaultConfig.targetSdk, + "--manifest", stubXml, + "--java", outSrcDir, compileTmp) + standardOutput = dummy + errorOutput = dummy + } + + exec { + commandLine(aapt, "optimize", + "-o", optTmp, + "--collapse-resource-names", linkTmp) + standardOutput = dummy + errorOutput = dummy + } + + genEncryptedResources(optTmp, outSrcDir) + } finally { + compileTmp.delete() + linkTmp.delete() + optTmp.delete() + stubXml.delete() + } + } + } + registerJavaGeneratingTask(genSrcTask, outSrcDir) +} + dependencies { implementation(project(":app:shared")) } diff --git a/stub/proguard-rules.pro b/stub/proguard-rules.pro index f352d3fa9..99c641428 100644 --- a/stub/proguard-rules.pro +++ b/stub/proguard-rules.pro @@ -20,6 +20,10 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile +-obfuscationdictionary ../dict.txt +-classobfuscationdictionary ../dict.txt +-packageobfuscationdictionary ../dict.txt + # Excessive obfuscation -repackageclasses -allowaccessmodification diff --git a/stub/src/main/res/values-ar/strings.xml b/stub/res/values-ar/strings.xml similarity index 100% rename from stub/src/main/res/values-ar/strings.xml rename to stub/res/values-ar/strings.xml diff --git a/stub/src/main/res/values-az/strings.xml b/stub/res/values-az/strings.xml similarity index 100% rename from stub/src/main/res/values-az/strings.xml rename to stub/res/values-az/strings.xml diff --git a/stub/src/main/res/values-be/strings.xml b/stub/res/values-be/strings.xml similarity index 100% rename from stub/src/main/res/values-be/strings.xml rename to stub/res/values-be/strings.xml diff --git a/stub/src/main/res/values-bg/strings.xml b/stub/res/values-bg/strings.xml similarity index 100% rename from stub/src/main/res/values-bg/strings.xml rename to stub/res/values-bg/strings.xml diff --git a/stub/src/main/res/values-ca/strings.xml b/stub/res/values-ca/strings.xml similarity index 100% rename from stub/src/main/res/values-ca/strings.xml rename to stub/res/values-ca/strings.xml diff --git a/stub/src/main/res/values-cs/strings.xml b/stub/res/values-cs/strings.xml similarity index 100% rename from stub/src/main/res/values-cs/strings.xml rename to stub/res/values-cs/strings.xml diff --git a/stub/src/main/res/values-de/strings.xml b/stub/res/values-de/strings.xml similarity index 100% rename from stub/src/main/res/values-de/strings.xml rename to stub/res/values-de/strings.xml diff --git a/stub/src/main/res/values-el/strings.xml b/stub/res/values-el/strings.xml similarity index 100% rename from stub/src/main/res/values-el/strings.xml rename to stub/res/values-el/strings.xml diff --git a/stub/src/main/res/values-es/strings.xml b/stub/res/values-es/strings.xml similarity index 100% rename from stub/src/main/res/values-es/strings.xml rename to stub/res/values-es/strings.xml diff --git a/stub/src/main/res/values-et/strings.xml b/stub/res/values-et/strings.xml similarity index 100% rename from stub/src/main/res/values-et/strings.xml rename to stub/res/values-et/strings.xml diff --git a/stub/src/main/res/values-fa/strings.xml b/stub/res/values-fa/strings.xml similarity index 100% rename from stub/src/main/res/values-fa/strings.xml rename to stub/res/values-fa/strings.xml diff --git a/stub/src/main/res/values-fr/strings.xml b/stub/res/values-fr/strings.xml similarity index 100% rename from stub/src/main/res/values-fr/strings.xml rename to stub/res/values-fr/strings.xml diff --git a/stub/src/main/res/values-hi/strings.xml b/stub/res/values-hi/strings.xml similarity index 100% rename from stub/src/main/res/values-hi/strings.xml rename to stub/res/values-hi/strings.xml diff --git a/stub/src/main/res/values-hr/strings.xml b/stub/res/values-hr/strings.xml similarity index 100% rename from stub/src/main/res/values-hr/strings.xml rename to stub/res/values-hr/strings.xml diff --git a/stub/src/main/res/values-in/strings.xml b/stub/res/values-in/strings.xml similarity index 100% rename from stub/src/main/res/values-in/strings.xml rename to stub/res/values-in/strings.xml diff --git a/stub/src/main/res/values-it/strings.xml b/stub/res/values-it/strings.xml similarity index 100% rename from stub/src/main/res/values-it/strings.xml rename to stub/res/values-it/strings.xml diff --git a/stub/src/main/res/values-iw/strings.xml b/stub/res/values-iw/strings.xml similarity index 100% rename from stub/src/main/res/values-iw/strings.xml rename to stub/res/values-iw/strings.xml diff --git a/stub/src/main/res/values-ja/strings.xml b/stub/res/values-ja/strings.xml similarity index 100% rename from stub/src/main/res/values-ja/strings.xml rename to stub/res/values-ja/strings.xml diff --git a/stub/src/main/res/values-ka/strings.xml b/stub/res/values-ka/strings.xml similarity index 100% rename from stub/src/main/res/values-ka/strings.xml rename to stub/res/values-ka/strings.xml diff --git a/stub/src/main/res/values-ko/strings.xml b/stub/res/values-ko/strings.xml similarity index 100% rename from stub/src/main/res/values-ko/strings.xml rename to stub/res/values-ko/strings.xml diff --git a/stub/src/main/res/values-lt/strings.xml b/stub/res/values-lt/strings.xml similarity index 100% rename from stub/src/main/res/values-lt/strings.xml rename to stub/res/values-lt/strings.xml diff --git a/stub/src/main/res/values-mk/strings.xml b/stub/res/values-mk/strings.xml similarity index 100% rename from stub/src/main/res/values-mk/strings.xml rename to stub/res/values-mk/strings.xml diff --git a/stub/src/main/res/values-nb/strings.xml b/stub/res/values-nb/strings.xml similarity index 100% rename from stub/src/main/res/values-nb/strings.xml rename to stub/res/values-nb/strings.xml diff --git a/stub/src/main/res/values-nl/strings.xml b/stub/res/values-nl/strings.xml similarity index 100% rename from stub/src/main/res/values-nl/strings.xml rename to stub/res/values-nl/strings.xml diff --git a/stub/src/main/res/values-pa/strings.xml b/stub/res/values-pa/strings.xml similarity index 100% rename from stub/src/main/res/values-pa/strings.xml rename to stub/res/values-pa/strings.xml diff --git a/stub/src/main/res/values-pl/strings.xml b/stub/res/values-pl/strings.xml similarity index 100% rename from stub/src/main/res/values-pl/strings.xml rename to stub/res/values-pl/strings.xml diff --git a/stub/src/main/res/values-pt-rBR/strings.xml b/stub/res/values-pt-rBR/strings.xml similarity index 100% rename from stub/src/main/res/values-pt-rBR/strings.xml rename to stub/res/values-pt-rBR/strings.xml diff --git a/stub/src/main/res/values-pt-rPT/strings.xml b/stub/res/values-pt-rPT/strings.xml similarity index 100% rename from stub/src/main/res/values-pt-rPT/strings.xml rename to stub/res/values-pt-rPT/strings.xml diff --git a/stub/src/main/res/values-ro/strings.xml b/stub/res/values-ro/strings.xml similarity index 100% rename from stub/src/main/res/values-ro/strings.xml rename to stub/res/values-ro/strings.xml diff --git a/stub/src/main/res/values-ru/strings.xml b/stub/res/values-ru/strings.xml similarity index 100% rename from stub/src/main/res/values-ru/strings.xml rename to stub/res/values-ru/strings.xml diff --git a/stub/src/main/res/values-sk/strings.xml b/stub/res/values-sk/strings.xml similarity index 100% rename from stub/src/main/res/values-sk/strings.xml rename to stub/res/values-sk/strings.xml diff --git a/stub/src/main/res/values-sr/strings.xml b/stub/res/values-sr/strings.xml similarity index 100% rename from stub/src/main/res/values-sr/strings.xml rename to stub/res/values-sr/strings.xml diff --git a/stub/src/main/res/values-sv/strings.xml b/stub/res/values-sv/strings.xml similarity index 100% rename from stub/src/main/res/values-sv/strings.xml rename to stub/res/values-sv/strings.xml diff --git a/stub/src/main/res/values-ta/strings.xml b/stub/res/values-ta/strings.xml similarity index 100% rename from stub/src/main/res/values-ta/strings.xml rename to stub/res/values-ta/strings.xml diff --git a/stub/src/main/res/values-th/strings.xml b/stub/res/values-th/strings.xml similarity index 100% rename from stub/src/main/res/values-th/strings.xml rename to stub/res/values-th/strings.xml diff --git a/stub/src/main/res/values-tr/strings.xml b/stub/res/values-tr/strings.xml similarity index 100% rename from stub/src/main/res/values-tr/strings.xml rename to stub/res/values-tr/strings.xml diff --git a/stub/src/main/res/values-uk/strings.xml b/stub/res/values-uk/strings.xml similarity index 100% rename from stub/src/main/res/values-uk/strings.xml rename to stub/res/values-uk/strings.xml diff --git a/stub/src/main/res/values-vi/strings.xml b/stub/res/values-vi/strings.xml similarity index 100% rename from stub/src/main/res/values-vi/strings.xml rename to stub/res/values-vi/strings.xml diff --git a/stub/src/main/res/values-zh-rCN/strings.xml b/stub/res/values-zh-rCN/strings.xml similarity index 100% rename from stub/src/main/res/values-zh-rCN/strings.xml rename to stub/res/values-zh-rCN/strings.xml diff --git a/stub/src/main/res/values-zh-rTW/strings.xml b/stub/res/values-zh-rTW/strings.xml similarity index 100% rename from stub/src/main/res/values-zh-rTW/strings.xml rename to stub/res/values-zh-rTW/strings.xml diff --git a/stub/src/main/res/values/strings.xml b/stub/res/values/strings.xml similarity index 100% rename from stub/src/main/res/values/strings.xml rename to stub/res/values/strings.xml diff --git a/stub/src/main/AndroidManifest.xml b/stub/src/main/AndroidManifest.xml deleted file mode 100644 index 6a0c9ce31..000000000 --- a/stub/src/main/AndroidManifest.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/stub/src/main/java/a/Q.java b/stub/src/main/java/a/Q.java deleted file mode 100644 index 537e6615e..000000000 --- a/stub/src/main/java/a/Q.java +++ /dev/null @@ -1,5 +0,0 @@ -package a; - -import com.topjohnwu.magisk.DelegateApplication; - -public class Q extends DelegateApplication {} diff --git a/stub/src/main/java/a/z.java b/stub/src/main/java/a/z.java deleted file mode 100644 index 883e9cebb..000000000 --- a/stub/src/main/java/a/z.java +++ /dev/null @@ -1,5 +0,0 @@ -package a; - -import com.topjohnwu.magisk.DelegateComponentFactory; - -public class z extends DelegateComponentFactory {} diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java index f1f9f637a..84d7b794f 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java @@ -7,6 +7,9 @@ import android.content.res.Configuration; import java.lang.reflect.Method; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class DelegateApplication extends Application { static boolean dynLoad = false; diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index b19609460..7206b9cee 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -1,5 +1,14 @@ package com.topjohnwu.magisk; +import static android.R.string.no; +import static android.R.string.ok; +import static android.R.string.yes; +import static com.topjohnwu.magisk.DelegateApplication.dynLoad; +import static com.topjohnwu.magisk.A.string.dling; +import static com.topjohnwu.magisk.A.string.no_internet_msg; +import static com.topjohnwu.magisk.A.string.relaunch_app; +import static com.topjohnwu.magisk.A.string.upgrade_msg; + import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -16,17 +25,22 @@ import com.topjohnwu.magisk.utils.APKInstall; import org.json.JSONException; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; -import static android.R.string.no; -import static android.R.string.ok; -import static android.R.string.yes; -import static com.topjohnwu.magisk.DelegateApplication.dynLoad; -import static com.topjohnwu.magisk.R.string.dling; -import static com.topjohnwu.magisk.R.string.no_internet_msg; -import static com.topjohnwu.magisk.R.string.relaunch_app; -import static com.topjohnwu.magisk.R.string.upgrade_msg; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class DownloadActivity extends Activity { private static final String APP_NAME = "Magisk"; @@ -41,6 +55,9 @@ public class DownloadActivity extends Activity { super.onCreate(savedInstanceState); themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault); + // Inject resources + loadResources(); + if (Networking.checkNetworkStatus(this)) { if (apkLink == null) { fetchCanary(); @@ -111,4 +128,26 @@ public class DownloadActivity extends Activity { }); } + private void loadResources() { + File apk = new File(getCacheDir(), "res.apk"); + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKey key = new SecretKeySpec(Bytes.key(), "AES"); + IvParameterSpec iv = new IvParameterSpec(Bytes.iv()); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + InputStream is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher); + try (InputStream gzip = new GZIPInputStream(is); + OutputStream out = new FileOutputStream(apk)) { + byte[] buf = new byte[4096]; + for (int read; (read = gzip.read(buf)) >= 0;) { + out.write(buf, 0, read); + } + } + DynAPK.addAssetPath(getResources().getAssets(), apk.getPath()); + } catch (Exception e) { + // Should not happen + e.printStackTrace(); + } + } + } diff --git a/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java b/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java index eaab4d9fb..4d2d68169 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java +++ b/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java @@ -17,6 +17,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class InjectAPK { static Object componentFactory; diff --git a/stub/src/main/java/com/topjohnwu/magisk/Mapping.java b/stub/src/main/java/com/topjohnwu/magisk/Mapping.java deleted file mode 100644 index 1f4e17be3..000000000 --- a/stub/src/main/java/com/topjohnwu/magisk/Mapping.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.topjohnwu.magisk; - -import com.topjohnwu.magisk.dummy.DummyReceiver; - -import java.util.HashMap; -import java.util.Map; - -/** - * These are just some random class names hardcoded as an example. - * For the actual release builds, these mappings will be auto generated. - */ -public class Mapping { - - private static final Map map = new HashMap<>(); - public static final Map> internalMap = new HashMap<>(); - public static final Map inverseMap; - - static { - map.put("a.Q", "com.topjohnwu.magisk.core.App"); - map.put("f.u7", "com.topjohnwu.magisk.core.SplashActivity"); - map.put("fxQ.lk", "com.topjohnwu.magisk.core.Provider"); - map.put("yy.E", "com.topjohnwu.magisk.core.Receiver"); - map.put("xt.R", "com.topjohnwu.magisk.ui.MainActivity"); - map.put("lt5.a", "com.topjohnwu.magisk.ui.surequest.SuRequestActivity"); - map.put("d.s", "com.topjohnwu.magisk.core.download.DownloadService"); - map.put("w.d", "androidx.work.impl.background.systemjob.SystemJobService"); - - internalMap.put("a.Q", DelegateApplication.class); - internalMap.put("f.u7", DownloadActivity.class); - internalMap.put("fxQ.lk", FileProvider.class); - internalMap.put("yy.E", DummyReceiver.class); - - inverseMap = new HashMap<>(map.size()); - for (Map.Entry e : map.entrySet()) { - inverseMap.put(e.getValue(), e.getKey()); - } - } - - public static String get(String name) { - String n = map.get(name); - return n != null ? n : name; - } -} diff --git a/stub/src/main/java/com/topjohnwu/magisk/net/Networking.java b/stub/src/main/java/com/topjohnwu/magisk/net/Networking.java index bc93dc48a..470996ff8 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/net/Networking.java +++ b/stub/src/main/java/com/topjohnwu/magisk/net/Networking.java @@ -10,6 +10,9 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class Networking { private static final int READ_TIMEOUT = 15000; diff --git a/stub/src/main/java/com/topjohnwu/magisk/net/Request.java b/stub/src/main/java/com/topjohnwu/magisk/net/Request.java index f0373eb63..4c1c55de7 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/net/Request.java +++ b/stub/src/main/java/com/topjohnwu/magisk/net/Request.java @@ -20,6 +20,9 @@ import java.net.HttpURLConnection; import java.util.Scanner; import java.util.concurrent.Executor; +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate public class Request implements Closeable { private HttpURLConnection conn; private Executor executor = null; diff --git a/stub/template/AndroidManifest.xml b/stub/template/AndroidManifest.xml new file mode 100644 index 000000000..952c391a8 --- /dev/null +++ b/stub/template/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + +%s + + + + diff --git a/stub/template/Mapping.java b/stub/template/Mapping.java new file mode 100644 index 000000000..0f6ddcd55 --- /dev/null +++ b/stub/template/Mapping.java @@ -0,0 +1,26 @@ +package com.topjohnwu.magisk; + +import java.util.HashMap; +import java.util.Map; + +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate +public class Mapping { + + private static final Map map = new HashMap<>(); + public static final Map> internalMap = new HashMap<>(); + public static final Map inverseMap = new HashMap<>(); + + static { +%s + for (Map.Entry e : map.entrySet()) { + inverseMap.put(e.getValue(), e.getKey()); + } + } + + public static String get(String name) { + String n = map.get(name); + return n != null ? n : name; + } +}