mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-01-03 04:47:42 +00:00
Open source fully obfuscated stub
This commit is contained in:
parent
a967afc629
commit
9c09ad3b62
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ out
|
|||||||
*.apk
|
*.apk
|
||||||
/config.prop
|
/config.prop
|
||||||
/update.sh
|
/update.sh
|
||||||
|
/dict.txt
|
||||||
|
|
||||||
# Built binaries
|
# Built binaries
|
||||||
native/out
|
native/out
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
import java.io.PrintStream
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@ -143,32 +142,11 @@ android.applicationVariants.all {
|
|||||||
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
|
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
|
||||||
val outSrc = File(outSrcDir, "com/topjohnwu/magisk/signing/KeyData.java")
|
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") {
|
val genSrcTask = tasks.register("generate${name.capitalize()}KeyData") {
|
||||||
inputs.dir(keysDir)
|
inputs.dir(keysDir)
|
||||||
outputs.file(outSrc)
|
outputs.file(outSrc)
|
||||||
doLast {
|
doLast {
|
||||||
outSrc.parentFile.mkdirs()
|
genKeyData(keysDir, outSrc)
|
||||||
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("}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registerJavaGeneratingTask(genSrcTask, outSrcDir)
|
registerJavaGeneratingTask(genSrcTask, outSrcDir)
|
||||||
|
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@ -49,6 +49,10 @@
|
|||||||
-repackageclasses 'a'
|
-repackageclasses 'a'
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
|
-obfuscationdictionary ../dict.txt
|
||||||
|
-classobfuscationdictionary ../dict.txt
|
||||||
|
-packageobfuscationdictionary ../dict.txt
|
||||||
|
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
|
@ -8,3 +8,7 @@ android {
|
|||||||
consumerProguardFiles("proguard-rules.pro")
|
consumerProguardFiles("proguard-rules.pro")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api("io.michaelrocks:paranoid-core:0.3.5")
|
||||||
|
}
|
||||||
|
@ -24,4 +24,9 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
|
<!-- Hardcode GMS version -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="12451000" />
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
@ -7,8 +9,9 @@ import java.io.File;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class DynAPK {
|
public class DynAPK {
|
||||||
|
|
||||||
// Indices of the object array
|
// Indices of the object array
|
||||||
|
@ -19,9 +19,12 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified from androidx.core.content.FileProvider
|
* Modified from androidx.core.content.FileProvider
|
||||||
*/
|
*/
|
||||||
|
@Obfuscate
|
||||||
public class FileProvider extends ContentProvider {
|
public class FileProvider extends ContentProvider {
|
||||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ package com.topjohnwu.magisk;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class ProviderInstaller {
|
public class ProviderInstaller {
|
||||||
|
|
||||||
public static boolean install(Context context) {
|
public static boolean install(Context context) {
|
||||||
|
@ -12,6 +12,9 @@ import com.topjohnwu.magisk.FileProvider;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class APKInstall {
|
public class APKInstall {
|
||||||
|
|
||||||
public static Intent installIntent(Context c, File apk) {
|
public static Intent installIntent(Context c, File apk) {
|
||||||
|
@ -71,11 +71,6 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.version"
|
|
||||||
android:value="12451000" />
|
|
||||||
|
|
||||||
<!-- Initialize WorkManager on-demand -->
|
<!-- Initialize WorkManager on-demand -->
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
@ -26,6 +26,7 @@ class MagiskPlugin : Plugin<Project> {
|
|||||||
override fun apply(project: Project) = project.applyPlugin()
|
override fun apply(project: Project) = project.applyPlugin()
|
||||||
|
|
||||||
private fun Project.applyPlugin() {
|
private fun Project.applyPlugin() {
|
||||||
|
initRandom(rootProject.file("dict.txt"))
|
||||||
props.clear()
|
props.clear()
|
||||||
rootProject.file("gradle.properties").inputStream().use { props.load(it) }
|
rootProject.file("gradle.properties").inputStream().use { props.load(it) }
|
||||||
val configPath: String? by this
|
val configPath: String? by this
|
||||||
|
286
buildSrc/src/main/java/Codegen.kt
Normal file
286
buildSrc/src/main/java/Codegen.kt
Normal file
@ -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<String>()
|
||||||
|
private val c2 = mutableListOf<String>()
|
||||||
|
private val c3 = mutableListOf<String>()
|
||||||
|
|
||||||
|
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 <T> chain(vararg iters: Iterable<T>) = 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<Component>()
|
||||||
|
|
||||||
|
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",
|
||||||
|
"""
|
||||||
|
|<provider
|
||||||
|
| android:name="%s"
|
||||||
|
| android:authorities="${'$'}{applicationId}.provider"
|
||||||
|
| android:directBootAware="true"
|
||||||
|
| android:exported="false"
|
||||||
|
| android:grantUriPermissions="true" />""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"com.topjohnwu.magisk.core.Receiver",
|
||||||
|
"dummy.DummyReceiver",
|
||||||
|
"""
|
||||||
|
|<receiver
|
||||||
|
| android:name="%s"
|
||||||
|
| android:directBootAware="true"
|
||||||
|
| android:exported="false">
|
||||||
|
| <intent-filter>
|
||||||
|
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||||
|
| <action android:name="android.intent.action.UID_REMOVED" />
|
||||||
|
| </intent-filter>
|
||||||
|
| <intent-filter>
|
||||||
|
| <action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
|
| <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||||
|
|
|
||||||
|
| <data android:scheme="package" />
|
||||||
|
| </intent-filter>
|
||||||
|
|</receiver>""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"com.topjohnwu.magisk.core.SplashActivity",
|
||||||
|
"DownloadActivity",
|
||||||
|
"""
|
||||||
|
|<activity
|
||||||
|
| android:name="%s"
|
||||||
|
| android:exported="true">
|
||||||
|
| <intent-filter>
|
||||||
|
| <action android:name="android.intent.action.MAIN" />
|
||||||
|
| <category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
| </intent-filter>
|
||||||
|
|</activity>""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"com.topjohnwu.magisk.ui.MainActivity",
|
||||||
|
"",
|
||||||
|
"""|<activity android:name="%s" />""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"com.topjohnwu.magisk.ui.surequest.SuRequestActivity",
|
||||||
|
"",
|
||||||
|
"""
|
||||||
|
|<activity
|
||||||
|
| android:name="%s"
|
||||||
|
| android:directBootAware="true"
|
||||||
|
| android:excludeFromRecents="true"
|
||||||
|
| android:exported="false"
|
||||||
|
| tools:ignore="AppLinkUrlError">
|
||||||
|
| <intent-filter>
|
||||||
|
| <action android:name="android.intent.action.VIEW"/>
|
||||||
|
| <category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
| </intent-filter>
|
||||||
|
|</activity>""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"com.topjohnwu.magisk.core.download.DownloadService",
|
||||||
|
"",
|
||||||
|
"""|<service android:name="%s" />""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
cmpList.add(Component(
|
||||||
|
"androidx.work.impl.background.systemjob.SystemJobService",
|
||||||
|
"",
|
||||||
|
"""
|
||||||
|
|<service
|
||||||
|
| android:name="%s"
|
||||||
|
| android:directBootAware="false"
|
||||||
|
| android:enabled="true"
|
||||||
|
| android:exported="true"
|
||||||
|
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
|
||||||
|
))
|
||||||
|
|
||||||
|
val names = mutableListOf<String>()
|
||||||
|
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<String>()
|
||||||
|
val usedNames = mutableListOf<String>()
|
||||||
|
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("}")
|
||||||
|
}
|
||||||
|
}
|
1
stub/.gitignore
vendored
1
stub/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/build
|
/build
|
||||||
|
/src/main/AndroidManifest.xml
|
||||||
|
@ -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 {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("io.michaelrocks:paranoid-gradle-plugin:0.3.5")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(plugin = "io.michaelrocks.paranoid")
|
||||||
|
|
||||||
|
extensions.configure<ParanoidExtension>("paranoid") {
|
||||||
|
obfuscationSeed = if (RAND_SEED != 0) RAND_SEED else null
|
||||||
|
includeSubprojects = true
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
val canary = !Config.version.contains(".")
|
val canary = !Config.version.contains(".")
|
||||||
|
|
||||||
@ -11,7 +34,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.topjohnwu.magisk"
|
applicationId = "com.topjohnwu.magisk"
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = Config.version
|
versionName = "1.0"
|
||||||
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
||||||
buildConfigField("String", "APK_URL", url?.let { "\"$it\"" } ?: "null" )
|
buildConfigField("String", "APK_URL", url?.let { "\"$it\"" } ?: "null" )
|
||||||
}
|
}
|
||||||
@ -24,16 +47,98 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidResources {
|
|
||||||
additionalParameters("--package-id", "0x80")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
includeInApk = false
|
includeInApk = false
|
||||||
includeInBundle = 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("<manifest package=\"com.topjohnwu.magisk\"/>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("<manifest package=\"com.topjohnwu.magisk\"/>")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
dependencies {
|
||||||
implementation(project(":app:shared"))
|
implementation(project(":app:shared"))
|
||||||
}
|
}
|
||||||
|
4
stub/proguard-rules.pro
vendored
4
stub/proguard-rules.pro
vendored
@ -20,6 +20,10 @@
|
|||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-obfuscationdictionary ../dict.txt
|
||||||
|
-classobfuscationdictionary ../dict.txt
|
||||||
|
-packageobfuscationdictionary ../dict.txt
|
||||||
|
|
||||||
# Excessive obfuscation
|
# Excessive obfuscation
|
||||||
-repackageclasses
|
-repackageclasses
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="com.topjohnwu.magisk">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:name="a.Q"
|
|
||||||
android:appComponentFactory="a.z"
|
|
||||||
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon,UnusedAttribute">
|
|
||||||
|
|
||||||
<!-- Splash -->
|
|
||||||
<activity
|
|
||||||
android:name="f.u7"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<!-- Main -->
|
|
||||||
<activity android:name="xt.R" />
|
|
||||||
|
|
||||||
<!-- Superuser -->
|
|
||||||
<activity
|
|
||||||
android:name="lt5.a"
|
|
||||||
android:directBootAware="true"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:exported="false"
|
|
||||||
tools:ignore="AppLinkUrlError">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<!-- Receiver -->
|
|
||||||
<receiver
|
|
||||||
android:name="yy.E"
|
|
||||||
android:directBootAware="true"
|
|
||||||
android:exported="false">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
|
||||||
<action android:name="android.intent.action.UID_REMOVED" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
|
||||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
|
||||||
|
|
||||||
<data android:scheme="package" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<!-- DownloadService -->
|
|
||||||
<service android:name="d.s" />
|
|
||||||
|
|
||||||
<!-- FileProvider -->
|
|
||||||
<provider
|
|
||||||
android:name="fxQ.lk"
|
|
||||||
android:authorities="${applicationId}.provider"
|
|
||||||
android:directBootAware="true"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true" />
|
|
||||||
|
|
||||||
<!-- WorkManager -->
|
|
||||||
<service
|
|
||||||
android:name="w.d"
|
|
||||||
android:directBootAware="false"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.version"
|
|
||||||
android:value="12451000" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
@ -1,5 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.DelegateApplication;
|
|
||||||
|
|
||||||
public class Q extends DelegateApplication {}
|
|
@ -1,5 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.DelegateComponentFactory;
|
|
||||||
|
|
||||||
public class z extends DelegateComponentFactory {}
|
|
@ -7,6 +7,9 @@ import android.content.res.Configuration;
|
|||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class DelegateApplication extends Application {
|
public class DelegateApplication extends Application {
|
||||||
|
|
||||||
static boolean dynLoad = false;
|
static boolean dynLoad = false;
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
package com.topjohnwu.magisk;
|
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.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
@ -16,17 +25,22 @@ import com.topjohnwu.magisk.utils.APKInstall;
|
|||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
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 javax.crypto.Cipher;
|
||||||
import static android.R.string.ok;
|
import javax.crypto.CipherInputStream;
|
||||||
import static android.R.string.yes;
|
import javax.crypto.SecretKey;
|
||||||
import static com.topjohnwu.magisk.DelegateApplication.dynLoad;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import static com.topjohnwu.magisk.R.string.dling;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
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 io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class DownloadActivity extends Activity {
|
public class DownloadActivity extends Activity {
|
||||||
|
|
||||||
private static final String APP_NAME = "Magisk";
|
private static final String APP_NAME = "Magisk";
|
||||||
@ -41,6 +55,9 @@ public class DownloadActivity extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault);
|
themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault);
|
||||||
|
|
||||||
|
// Inject resources
|
||||||
|
loadResources();
|
||||||
|
|
||||||
if (Networking.checkNetworkStatus(this)) {
|
if (Networking.checkNetworkStatus(this)) {
|
||||||
if (apkLink == null) {
|
if (apkLink == null) {
|
||||||
fetchCanary();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class InjectAPK {
|
public class InjectAPK {
|
||||||
|
|
||||||
static Object componentFactory;
|
static Object componentFactory;
|
||||||
|
@ -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<String, String> map = new HashMap<>();
|
|
||||||
public static final Map<String, Class<?>> internalMap = new HashMap<>();
|
|
||||||
public static final Map<String, String> 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<String, String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,9 @@ import java.io.IOException;
|
|||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class Networking {
|
public class Networking {
|
||||||
|
|
||||||
private static final int READ_TIMEOUT = 15000;
|
private static final int READ_TIMEOUT = 15000;
|
||||||
|
@ -20,6 +20,9 @@ import java.net.HttpURLConnection;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import io.michaelrocks.paranoid.Obfuscate;
|
||||||
|
|
||||||
|
@Obfuscate
|
||||||
public class Request implements Closeable {
|
public class Request implements Closeable {
|
||||||
private HttpURLConnection conn;
|
private HttpURLConnection conn;
|
||||||
private Executor executor = null;
|
private Executor executor = null;
|
||||||
|
20
stub/template/AndroidManifest.xml
Normal file
20
stub/template/AndroidManifest.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.topjohnwu.magisk">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:appComponentFactory="%s"
|
||||||
|
android:name="%s"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon,UnusedAttribute">
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
26
stub/template/Mapping.java
Normal file
26
stub/template/Mapping.java
Normal file
@ -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<String, String> map = new HashMap<>();
|
||||||
|
public static final Map<String, Class<?>> internalMap = new HashMap<>();
|
||||||
|
public static final Map<String, String> inverseMap = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
%s
|
||||||
|
for (Map.Entry<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user