mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-28 04:25:27 +00:00
Refactor PatchAPK code
This commit is contained in:
parent
2eed09ef1b
commit
e938e717b0
@ -1,19 +1,9 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.core.utils.PatchAPK;
|
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
public class a {
|
public class a {
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg) {
|
|
||||||
return PatchAPK.patch(in, out, pkg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg, String label) {
|
|
||||||
return PatchAPK.patch(in, out, pkg, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
BootSigner.main(args);
|
BootSigner.main(args);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
|
|||||||
.setContentText("")
|
.setContentText("")
|
||||||
}
|
}
|
||||||
val patched = File(apk.parent, "patched.apk")
|
val patched = File(apk.parent, "patched.apk")
|
||||||
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
|
PatchAPK.patch(apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
|
||||||
apk.delete()
|
apk.delete()
|
||||||
patched.renameTo(apk)
|
patched.renameTo(apk)
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.core.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Base64OutputStream
|
import android.util.Base64OutputStream
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.utils.PatchAPK.ALPHANUM
|
import com.topjohnwu.magisk.core.utils.PatchAPK.ALPHANUM
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
|
||||||
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
||||||
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.koin.core.context.GlobalContext
|
|
||||||
import org.koin.core.context.startKoin
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
@ -33,7 +29,7 @@ private interface CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class Keygen: CertKeyProvider {
|
class Keygen(context: Context) : CertKeyProvider {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ALIAS = "magisk"
|
private const val ALIAS = "magisk"
|
||||||
@ -70,9 +66,6 @@ class Keygen: CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// This object could possibly be accessed from an external app
|
|
||||||
// Get context from reflection into Android's framework
|
|
||||||
val context = InternalUtils.getContext()
|
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||||
val sig = info.signatures[0]
|
val sig = info.signatures[0]
|
||||||
@ -104,14 +97,6 @@ class Keygen: CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun init(): KeyStore {
|
private fun init(): KeyStore {
|
||||||
GlobalContext.getOrNull() ?: {
|
|
||||||
// Invoked externally, do some basic initialization
|
|
||||||
startKoin {
|
|
||||||
modules(koinModules)
|
|
||||||
}
|
|
||||||
Timber.plant(Timber.DebugTree())
|
|
||||||
}()
|
|
||||||
|
|
||||||
val raw = Config.keyStoreRaw
|
val raw = Config.keyStoreRaw
|
||||||
val ks = KeyStore.getInstance("PKCS12")
|
val ks = KeyStore.getInstance("PKCS12")
|
||||||
if (raw.isEmpty()) {
|
if (raw.isEmpty()) {
|
||||||
@ -135,7 +120,7 @@ class Keygen: CertKeyProvider {
|
|||||||
val dname = X500Name("CN=${randomString()}")
|
val dname = X500Name("CN=${randomString()}")
|
||||||
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
||||||
start.time, end.time, dname, kp.public)
|
start.time, end.time, dname, kp.public)
|
||||||
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
|
val signer = JcaContentSignerBuilder("SHA1WithRSA").build(kp.private)
|
||||||
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
// Store them into keystore
|
// Store them into keystore
|
||||||
|
@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
@ -11,14 +10,13 @@ import com.topjohnwu.magisk.core.Info
|
|||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.core.view.Notifications
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
import com.topjohnwu.signing.SignAPK
|
import com.topjohnwu.signing.SignAPK
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Single
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@ -28,12 +26,13 @@ import java.security.SecureRandom
|
|||||||
|
|
||||||
object PatchAPK {
|
object PatchAPK {
|
||||||
|
|
||||||
private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"
|
private const val ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
private val UPPERALPHA = LOWERALPHA.toUpperCase()
|
|
||||||
private val ALPHA = LOWERALPHA + UPPERALPHA
|
|
||||||
private const val DIGITS = "0123456789"
|
private const val DIGITS = "0123456789"
|
||||||
val ALPHANUM = ALPHA + DIGITS
|
const val ALPHANUM = ALPHA + DIGITS
|
||||||
private val ALPHANUMDOTS = "$ALPHANUM............"
|
private const val ALPHANUMDOTS = "$ALPHANUM............"
|
||||||
|
|
||||||
|
private const val APP_ID = "com.topjohnwu.magisk"
|
||||||
|
private const val APP_NAME = "Magisk Manager"
|
||||||
|
|
||||||
private fun genPackageName(prefix: String, length: Int): CharSequence {
|
private fun genPackageName(prefix: String, length: Int): CharSequence {
|
||||||
val builder = StringBuilder(length)
|
val builder = StringBuilder(length)
|
||||||
@ -81,18 +80,40 @@ object PatchAPK {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun patch(apk: String, out: String, pkg: CharSequence, label: CharSequence): Boolean {
|
||||||
|
try {
|
||||||
|
val jar = JarMap.open(apk)
|
||||||
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||||
|
val xml = jar.getRawData(je)
|
||||||
|
|
||||||
|
if (!findAndPatch(xml, APP_ID, pkg) ||
|
||||||
|
!findAndPatch(xml, APP_NAME, label))
|
||||||
|
return false
|
||||||
|
|
||||||
|
// Write apk changes
|
||||||
|
jar.getOutputStream(je).write(xml)
|
||||||
|
val keys = Keygen(get())
|
||||||
|
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun patchAndHide(context: Context, label: String): Boolean {
|
private fun patchAndHide(context: Context, label: String): Boolean {
|
||||||
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
|
val dlStub = !isRunningAsStub && SDK_INT >= 28 &&
|
||||||
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
|
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT
|
||||||
// If not running as stub, and we are compatible with stub, use stub
|
val src = if (dlStub) {
|
||||||
val stub = File(context.cacheDir, "stub.apk")
|
val stub = File(context.cacheDir, "stub.apk")
|
||||||
val svc = get<GithubRawServices>()
|
val svc = get<GithubRawServices>()
|
||||||
runCatching {
|
try {
|
||||||
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
||||||
it.writeTo(stub)
|
it.writeTo(stub)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
} catch (e: Exception) {
|
||||||
Timber.e(it)
|
Timber.e(e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
stub.path
|
stub.path
|
||||||
@ -102,7 +123,7 @@ object PatchAPK {
|
|||||||
|
|
||||||
// Generate a new random package name and signature
|
// Generate a new random package name and signature
|
||||||
val repack = File(context.cacheDir, "patched.apk")
|
val repack = File(context.cacheDir, "patched.apk")
|
||||||
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
val pkg = genPackageName("com.", APP_ID.length)
|
||||||
Config.keyStoreRaw = ""
|
Config.keyStoreRaw = ""
|
||||||
|
|
||||||
if (!patch(src, repack.path, pkg, label))
|
if (!patch(src, repack.path, pkg, label))
|
||||||
@ -115,67 +136,20 @@ object PatchAPK {
|
|||||||
|
|
||||||
Config.suManager = pkg.toString()
|
Config.suManager = pkg.toString()
|
||||||
Config.export()
|
Config.export()
|
||||||
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
|
Shell.su("pm uninstall $APP_ID").submit()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun patch(apk: String, out: String, pkg: CharSequence, 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: CharSequence, 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) {
|
fun hideManager(context: Context, label: String) {
|
||||||
Completable.fromAction {
|
|
||||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
||||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
||||||
if (!patchAndHide(context, label))
|
Single.fromCallable {
|
||||||
|
patchAndHide(context, label)
|
||||||
|
}.subscribeK {
|
||||||
|
if (!it)
|
||||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
||||||
}.subscribeK()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user