Refactor PatchAPK code

This commit is contained in:
topjohnwu 2020-01-31 03:37:39 +08:00
parent 2eed09ef1b
commit e938e717b0
4 changed files with 48 additions and 99 deletions

View File

@ -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);
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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() }
} }
} }