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;
import com.topjohnwu.magisk.core.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner;
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 {
BootSigner.main(args);
}

View File

@ -27,7 +27,7 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
.setContentText("")
}
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()
patched.renameTo(apk)
}

View File

@ -1,21 +1,17 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Base64OutputStream
import com.topjohnwu.magisk.core.Config
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.readPrivateKey
import com.topjohnwu.superuser.internal.InternalUtils
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
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.math.BigInteger
import java.security.KeyPairGenerator
@ -33,7 +29,7 @@ private interface CertKeyProvider {
}
@Suppress("DEPRECATION")
class Keygen: CertKeyProvider {
class Keygen(context: Context) : CertKeyProvider {
companion object {
private const val ALIAS = "magisk"
@ -70,9 +66,6 @@ class Keygen: CertKeyProvider {
}
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 info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val sig = info.signatures[0]
@ -104,14 +97,6 @@ class Keygen: CertKeyProvider {
}
private fun init(): KeyStore {
GlobalContext.getOrNull() ?: {
// Invoked externally, do some basic initialization
startKoin {
modules(koinModules)
}
Timber.plant(Timber.DebugTree())
}()
val raw = Config.keyStoreRaw
val ks = KeyStore.getInstance("PKCS12")
if (raw.isEmpty()) {
@ -135,7 +120,7 @@ class Keygen: CertKeyProvider {
val dname = X500Name("CN=${randomString()}")
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
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))
// Store them into keystore

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.widget.Toast
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
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.view.Notifications
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.signing.JarMap
import com.topjohnwu.signing.SignAPK
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.Single
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
@ -28,12 +26,13 @@ import java.security.SecureRandom
object PatchAPK {
private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"
private val UPPERALPHA = LOWERALPHA.toUpperCase()
private val ALPHA = LOWERALPHA + UPPERALPHA
private const val ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private const val DIGITS = "0123456789"
val ALPHANUM = ALPHA + DIGITS
private val ALPHANUMDOTS = "$ALPHANUM............"
const val ALPHANUM = ALPHA + DIGITS
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 {
val builder = StringBuilder(length)
@ -81,18 +80,40 @@ object PatchAPK {
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 {
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 dlStub = !isRunningAsStub && SDK_INT >= 28 &&
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT
val src = if (dlStub) {
val stub = File(context.cacheDir, "stub.apk")
val svc = get<GithubRawServices>()
runCatching {
try {
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
it.writeTo(stub)
}
}.onFailure {
Timber.e(it)
} catch (e: Exception) {
Timber.e(e)
return false
}
stub.path
@ -102,7 +123,7 @@ object PatchAPK {
// Generate a new random package name and signature
val repack = File(context.cacheDir, "patched.apk")
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
val pkg = genPackageName("com.", APP_ID.length)
Config.keyStoreRaw = ""
if (!patch(src, repack.path, pkg, label))
@ -115,67 +136,20 @@ object PatchAPK {
Config.suManager = pkg.toString()
Config.export()
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
Shell.su("pm uninstall $APP_ID").submit()
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) {
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))
Single.fromCallable {
patchAndHide(context, label)
}.subscribeK {
if (!it)
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
}.subscribeK()
}
}
}