You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
177 lines
6.0 KiB
Kotlin
177 lines
6.0 KiB
Kotlin
package com.topjohnwu.magisk.utils
|
|
|
|
import android.content.Context
|
|
import android.os.Build.VERSION.SDK_INT
|
|
import android.widget.Toast
|
|
import com.topjohnwu.magisk.*
|
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
|
import com.topjohnwu.magisk.extensions.get
|
|
import com.topjohnwu.magisk.extensions.subscribeK
|
|
import com.topjohnwu.magisk.extensions.writeTo
|
|
import com.topjohnwu.magisk.view.Notifications
|
|
import com.topjohnwu.signing.JarMap
|
|
import com.topjohnwu.signing.SignAPK
|
|
import com.topjohnwu.superuser.Shell
|
|
import io.reactivex.Completable
|
|
import timber.log.Timber
|
|
import java.io.File
|
|
import java.io.FileOutputStream
|
|
import java.nio.ByteBuffer
|
|
import java.nio.ByteOrder
|
|
import java.security.SecureRandom
|
|
|
|
object PatchAPK {
|
|
|
|
private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"
|
|
private val UPPERALPHA = LOWERALPHA.toUpperCase()
|
|
private val ALPHA = LOWERALPHA + UPPERALPHA
|
|
private const val DIGITS = "0123456789"
|
|
val ALPHANUM = ALPHA + DIGITS
|
|
private val ALPHANUMDOTS = "$ALPHANUM............"
|
|
|
|
private fun genPackageName(prefix: String, length: Int): String {
|
|
val builder = StringBuilder(length)
|
|
builder.append(prefix)
|
|
val len = length - prefix.length
|
|
val random = SecureRandom()
|
|
var next: Char
|
|
var prev = prefix[prefix.length - 1]
|
|
for (i in 0 until len) {
|
|
next = if (prev == '.' || i == len - 1) {
|
|
ALPHA[random.nextInt(ALPHA.length)]
|
|
} else {
|
|
ALPHANUMDOTS[random.nextInt(ALPHANUMDOTS.length)]
|
|
}
|
|
builder.append(next)
|
|
prev = next
|
|
}
|
|
return builder.toString()
|
|
}
|
|
|
|
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
|
if (to.length > from.length)
|
|
return false
|
|
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
|
val offList = mutableListOf<Int>()
|
|
var i = 0
|
|
loop@ while (i < buf.length - from.length) {
|
|
for (j in from.indices) {
|
|
if (buf.get(i + j) != from[j]) {
|
|
++i
|
|
continue@loop
|
|
}
|
|
}
|
|
offList.add(i)
|
|
i += from.length
|
|
}
|
|
if (offList.isEmpty())
|
|
return false
|
|
|
|
val toBuf = to.toCharArray().copyOf(from.length)
|
|
for (off in offList) {
|
|
buf.position(off)
|
|
buf.put(toBuf)
|
|
}
|
|
return true
|
|
}
|
|
|
|
private fun patchAndHide(context: Context, label: String): Boolean {
|
|
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
|
|
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
|
|
// If not running as stub, and we are compatible with stub, use stub
|
|
val stub = File(context.cacheDir, "stub.apk")
|
|
val svc = get<GithubRawServices>()
|
|
runCatching {
|
|
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
|
it.writeTo(stub)
|
|
}
|
|
}.onFailure {
|
|
Timber.e(it)
|
|
return false
|
|
}
|
|
stub.path
|
|
} else {
|
|
context.packageCodePath
|
|
}
|
|
|
|
// Generate a new random package name and signature
|
|
val repack = File(context.cacheDir, "patched.apk")
|
|
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
|
Config.keyStoreRaw = ""
|
|
|
|
if (!patch(src, repack.path, pkg, label))
|
|
return false
|
|
|
|
// Install the application
|
|
repack.setReadable(true, false)
|
|
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
|
|
return false
|
|
|
|
Config.suManager = pkg
|
|
Config.export()
|
|
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
|
|
|
|
return true
|
|
}
|
|
|
|
@JvmStatic
|
|
@JvmOverloads
|
|
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
|
|
try {
|
|
val jar = JarMap.open(apk)
|
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
|
val xml = jar.getRawData(je)
|
|
|
|
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
|
!findAndPatch(xml, "Magisk Manager", label))
|
|
return false
|
|
|
|
// Write apk changes
|
|
jar.getOutputStream(je).write(xml)
|
|
val keys = Keygen()
|
|
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
|
|
try {
|
|
if (apk.length() < 1 shl 18) {
|
|
// APK is smaller than 256K, must be stub
|
|
return patch(apk.path, out.path, pkg, label)
|
|
}
|
|
|
|
// Try using the new APK to patch itself
|
|
val loader = DynamicClassLoader(apk)
|
|
val cls = loader.loadClass("a.a")
|
|
|
|
for (m in cls.declaredMethods) {
|
|
val pars = m.parameterTypes
|
|
if (pars.size == 4 && pars[0] == String::class.java) {
|
|
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
|
|
}
|
|
}
|
|
throw Exception("No matching method found")
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
// Fallback to use the current implementation
|
|
return patch(apk.path, out.path, pkg, label)
|
|
}
|
|
}
|
|
|
|
fun hideManager(context: Context, label: String) {
|
|
Completable.fromAction {
|
|
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
|
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
|
if (!patchAndHide(context, label))
|
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
|
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
|
}.subscribeK()
|
|
}
|
|
}
|