diff --git a/app/src/main/java/com/topjohnwu/magisk/Const.kt b/app/src/main/java/com/topjohnwu/magisk/Const.kt index 0088a63e7..344bca741 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Const.kt @@ -19,8 +19,8 @@ object Const { const val MAGISK_LOG = "/cache/magisk.log" // Versions - const val SNET_EXT_VER = 12 - const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107" + const val SNET_EXT_VER = 13 + const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc" const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77" // Misc diff --git a/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt b/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt index 8ed29d5ad..04cbadcfe 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/network/GithubServices.kt @@ -28,7 +28,7 @@ interface GithubRawServices { @GET fun fetchCustomUpdate(@Url url: String): Single - @GET("$MAGISK_FILES/{$REVISION}/snet.apk") + @GET("$MAGISK_FILES/{$REVISION}/snet.jar") @Streaming fun fetchSafetynet(@Path(REVISION) revision: String = Const.SNET_REVISION): Single diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 775ee86ef..3d5ca84fa 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -1,8 +1,6 @@ package com.topjohnwu.magisk.ui.home -import android.app.Activity import android.content.Context -import android.os.Bundle import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.viewevents.ViewEvent import com.topjohnwu.magisk.BuildConfig @@ -11,7 +9,6 @@ import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.databinding.FragmentMagiskBinding -import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.inject import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.model.events.* @@ -22,16 +19,20 @@ import com.topjohnwu.magisk.utils.SafetyNetHelper import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.dialogs.* import com.topjohnwu.superuser.Shell +import dalvik.system.DexFile +import io.reactivex.Completable import org.koin.androidx.viewmodel.ext.android.viewModel import java.io.File +import java.lang.reflect.InvocationHandler class HomeFragment : MagiskFragment(), SafetyNetHelper.Callback { override val layoutRes: Int = R.layout.fragment_magisk override val viewModel: HomeViewModel by viewModel() - val magiskRepo: MagiskRepository by inject() - lateinit var EXT_FILE: File + + private val magiskRepo: MagiskRepository by inject() + private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") } override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode) @@ -48,11 +49,6 @@ class HomeFragment : MagiskFragment(), } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - EXT_FILE = File("${requireActivity().filesDir.parent}/snet", "snet.apk") - } - override fun onStart() { super.onStart() setHasOptionsMenu(true) @@ -78,7 +74,7 @@ class HomeFragment : MagiskFragment(), private fun downloadSafetyNet(requiresUserInput: Boolean = true) { fun download() = magiskRepo.fetchSafetynet() - .map { it.byteStream().writeTo(EXT_FILE) } + .map { it.byteStream().writeTo(EXT_APK) } .subscribeK { updateSafetyNet(true) } if (!requiresUserInput) { @@ -96,29 +92,40 @@ class HomeFragment : MagiskFragment(), } private fun updateSafetyNet(dieOnError: Boolean) { - try { + Completable.fromAction { val loader = DynamicClassLoader(EXT_APK) - val clazz = loader.loadClass("com.topjohnwu.snet.Snet") - val helper = clazz.getMethod("newHelper", - Class::class.java, String::class.java, Activity::class.java, Any::class.java) - .invoke(null, SafetyNetHelper::class.java, EXT_APK.path, - activity, this) as SafetyNetHelper + val dex = DexFile.loadDex(EXT_APK.path, EXT_APK.parent, 0) + + // Scan through the dex and find our helper class + var helperClass: Class<*>? = null + for (className in dex.entries()) { + if (className.startsWith("x.")) { + val cls = loader.loadClass(className) + if (InvocationHandler::class.java.isAssignableFrom(cls)) { + helperClass = cls + break + } + } + } + helperClass ?: throw Exception() + + val helper = helperClass.getMethod("get", + Class::class.java, Context::class.java, Any::class.java) + .invoke(null, SafetyNetHelper::class.java, activity, this) as SafetyNetHelper + if (helper.version < Const.SNET_EXT_VER) throw Exception() + helper.attest() - } catch (e: Exception) { + }.subscribeK(onError = { if (dieOnError) { viewModel.finishSafetyNetCheck(-1) - return + } else { + Shell.sh("rm -rf " + EXT_APK.parent).exec() + EXT_APK.parentFile?.mkdir() + downloadSafetyNet(!dieOnError) } - Shell.sh("rm -rf " + EXT_APK.parent).exec() - EXT_APK.parentFile?.mkdir() - downloadSafetyNet(!dieOnError) - } - } - - companion object { - val EXT_APK by lazy { File("${get().filesDir.parent}/snet", "snet.apk") } + }) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index 62c4879b7..3775f8b7f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -1,11 +1,13 @@ package com.topjohnwu.magisk.ui.home +import android.content.pm.PackageManager import com.skoumal.teanity.extensions.addOnPropertyChangedCallback import com.skoumal.teanity.extensions.doOnSubscribeUi import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.util.KObservableField import com.topjohnwu.magisk.* import com.topjohnwu.magisk.data.repository.MagiskRepository +import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.packageName import com.topjohnwu.magisk.extensions.res import com.topjohnwu.magisk.extensions.toggle @@ -31,6 +33,10 @@ class HomeViewModel( private val magiskRepo: MagiskRepository ) : MagiskViewModel() { + val hasGMS = runCatching { + get().getPackageInfo("com.google.android.gms", 0); true + }.getOrElse { false } + val isAdvancedExpanded = KObservableField(false) val isForceEncryption = KObservableField(Info.keepEnc) diff --git a/app/src/main/res/layout/fragment_magisk.xml b/app/src/main/res/layout/fragment_magisk.xml index 4582cb0f5..763f2aaea 100644 --- a/app/src/main/res/layout/fragment_magisk.xml +++ b/app/src/main/res/layout/fragment_magisk.xml @@ -196,7 +196,7 @@ android:layout_margin="@dimen/margin_generic" /> diff --git a/build.py b/build.py index f92f61fbd..c0db8ed52 100755 --- a/build.py +++ b/build.py @@ -283,12 +283,11 @@ def build_snet(args): error('Build snet extention failed!') source = os.path.join('snet', 'build', 'outputs', 'apk', 'release', 'snet-release-unsigned.apk') - target = os.path.join(config['outdir'], 'snet.apk') - # Re-compress the whole APK for smaller size + target = os.path.join(config['outdir'], 'snet.jar') + # Extract classes.dex with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout: with zipfile.ZipFile(source) as zin: - for item in zin.infolist(): - zout.writestr(item.filename, zin.read(item)) + zout.writestr('classes.dex', zin.read('classes.dex')) rm(source) header('Output: ' + target) diff --git a/snet/build.gradle b/snet/build.gradle index 669a06b5d..37e6be2c0 100644 --- a/snet/build.gradle +++ b/snet/build.gradle @@ -4,7 +4,7 @@ android { defaultConfig { applicationId 'com.topjohnwu.snet' minSdkVersion 14 - versionCode 12 + versionCode 13 versionName 'snet' } @@ -19,5 +19,5 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.google.android.gms:play-services-safetynet:16.0.0' + implementation 'com.google.android.gms:play-services-safetynet:17.0.0' } diff --git a/snet/proguard-rules.pro b/snet/proguard-rules.pro index 83a35f656..934fdf271 100644 --- a/snet/proguard-rules.pro +++ b/snet/proguard-rules.pro @@ -20,7 +20,10 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class com.topjohnwu.snet.Snet { *; } +-keep,allowobfuscation class com.topjohnwu.snet.SafetyNetHelper +-keepclassmembers class com.topjohnwu.snet.SafetyNetHelper { + ** get(...); +} --repackageclasses '' +-repackageclasses 'x' -allowaccessmodification diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java index 34015a6bb..351b5f4f4 100644 --- a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java @@ -1,18 +1,14 @@ package com.topjohnwu.snet; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; import android.content.Context; -import android.content.Intent; import android.util.Base64; import android.util.Log; +import androidx.annotation.NonNull; + import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.api.ApiException; -import com.google.android.gms.common.internal.ConnectionErrorMessages; -import com.google.android.gms.common.internal.DialogRedirect; import com.google.android.gms.safetynet.SafetyNet; import com.google.android.gms.safetynet.SafetyNetApi; import com.google.android.gms.safetynet.SafetyNetClient; @@ -24,10 +20,9 @@ import org.json.JSONObject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.security.SecureRandom; -import androidx.annotation.NonNull; - public class SafetyNetHelper implements InvocationHandler, OnSuccessListener, OnFailureListener { @@ -40,11 +35,16 @@ public class SafetyNetHelper implements InvocationHandler, private static final SecureRandom RANDOM = new SecureRandom(); private static final String TAG = "SNET"; - private final Activity mActivity; + private final Context context; private final Object callback; - SafetyNetHelper(Activity activity, Object cb) { - mActivity = activity; + public static Object get(Class interfaceClass, Context context, Object cb) { + return Proxy.newProxyInstance(SafetyNetHelper.class.getClassLoader(), + new Class[]{interfaceClass}, new SafetyNetHelper(context, cb)); + } + + private SafetyNetHelper(Context c, Object cb) { + context = c; callback = cb; } @@ -52,7 +52,8 @@ public class SafetyNetHelper implements InvocationHandler, Class clazz = callback.getClass(); try { clazz.getMethod("onResponse", int.class).invoke(callback, code); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } /* Return magic API key here :) */ @@ -60,17 +61,14 @@ public class SafetyNetHelper implements InvocationHandler, return ""; } - /* Override ISafetyNetHelper.getVersion */ private int getVersion() { return BuildConfig.VERSION_CODE; } - /* Override ISafetyNetHelper.attest */ private void attest() { - int code = API_AVAIL.isGooglePlayServicesAvailable(mActivity); + int code = API_AVAIL.isGooglePlayServicesAvailable(context); if (code != ConnectionResult.SUCCESS) { - if (API_AVAIL.isUserResolvableError(code)) - getErrorDialog(code, 0).show(); + Log.e(TAG, API_AVAIL.getErrorString(code)); invokeCallback(CONNECTION_FAIL); return; } @@ -78,28 +76,10 @@ public class SafetyNetHelper implements InvocationHandler, byte[] nonce = new byte[24]; RANDOM.nextBytes(nonce); - SafetyNetClient client = SafetyNet.getClient(mActivity.getBaseContext()); + SafetyNetClient client = SafetyNet.getClient(context); client.attest(nonce, getApiKey()).addOnSuccessListener(this).addOnFailureListener(this); } - private Dialog getErrorDialog(int errorCode, int requestCode) { - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - Context swapCtx = new SwapResContext(mActivity, Snet.dexPath); - Intent intent = API_AVAIL.getErrorResolutionIntent(swapCtx, errorCode, "d"); - - builder.setMessage(ConnectionErrorMessages.getErrorMessage(swapCtx, errorCode)); - builder.setPositiveButton( - ConnectionErrorMessages.getErrorDialogButtonMessage(swapCtx, errorCode), - DialogRedirect.getInstance(mActivity, intent, requestCode)); - - String title; - if ((title = ConnectionErrorMessages.getErrorTitle(swapCtx, errorCode)) != null) { - builder.setTitle(title); - } - - return builder.create(); - } - @Override public void onSuccess(SafetyNetApi.AttestationResponse result) { int code = 0; @@ -121,12 +101,9 @@ public class SafetyNetHelper implements InvocationHandler, public void onFailure(@NonNull Exception e) { if (e instanceof ApiException) { int errCode = ((ApiException) e).getStatusCode(); - if (API_AVAIL.isUserResolvableError(errCode)) - getErrorDialog(errCode, 0).show(); - else - Log.e(TAG, "Cannot resolve: " + e.getMessage()); + Log.e(TAG, API_AVAIL.getErrorString(errCode)); } else { - Log.e(TAG, "Unknown: " + e.getMessage()); + Log.e(TAG, "Unknown: " + e); } invokeCallback(CONNECTION_FAIL); } @@ -136,11 +113,10 @@ public class SafetyNetHelper implements InvocationHandler, switch (method.getName()) { case "attest": attest(); - return null; + break; case "getVersion": return getVersion(); - default: - return null; } + return null; } } diff --git a/snet/src/main/java/com/topjohnwu/snet/Snet.java b/snet/src/main/java/com/topjohnwu/snet/Snet.java deleted file mode 100644 index 171e2a31c..000000000 --- a/snet/src/main/java/com/topjohnwu/snet/Snet.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.topjohnwu.snet; - -import android.app.Activity; - -import java.lang.reflect.Proxy; - -public class Snet { - static String dexPath; - - public static Object newHelper(Class interfaceClass, String dexPath, Activity activity, Object cb) { - Snet.dexPath = dexPath; - return Proxy.newProxyInstance(SafetyNetHelper.class.getClassLoader(), - new Class[] { interfaceClass }, new SafetyNetHelper(activity, cb)); - } -} diff --git a/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java b/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java deleted file mode 100644 index 1d765ed8a..000000000 --- a/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.topjohnwu.snet; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.AssetManager; -import android.content.res.Resources; - -public class SwapResContext extends ContextWrapper { - - private AssetManager asset; - private Resources resources; - - public SwapResContext(Context base, String apk) { - super(base); - try { - asset = AssetManager.class.newInstance(); - AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk); - } catch (Exception e) { - e.printStackTrace(); - } - Resources res = base.getResources(); - resources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration()); - } - - @Override - public Resources getResources() { - return resources; - } - - @Override - public AssetManager getAssets() { - return asset; - } -}