diff --git a/README.MD b/README.MD index 713460da8..778800efe 100644 --- a/README.MD +++ b/README.MD @@ -13,7 +13,6 @@ 2. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example. 3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h` 4. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `--release` flag), you need a Java Keystore file `release-key.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). -5. The SafetyNet extension pack requires the full Magisk Manager as a `compileOnly` dependency. Build the **release** APK, convert it back to Java `.class` files (I use [dex2jar](https://github.com/pxb1988/dex2jar)), and place the converted JAR under `snet/libs` before compiling. ## License diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java index 0b39f2deb..7e21f3d2b 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java @@ -45,10 +45,12 @@ public class CheckSafetyNet extends ParallelTask { private void dyload() throws Exception { DexClassLoader loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(), null, ISafetyNetHelper.class.getClassLoader()); - Class clazz = loader.loadClass("com.topjohnwu.snet.SafetyNetHelper"); - helper = (ISafetyNetHelper) clazz.getConstructors()[0] - .newInstance(getActivity(), (ISafetyNetHelper.Callback) - code -> MagiskManager.get().safetyNetDone.publish(false, code)); + Class clazz = loader.loadClass("com.topjohnwu.snet.Snet"); + helper = (ISafetyNetHelper) clazz.getMethod("newHelper", + Class.class, String.class, Activity.class, Object.class) + .invoke(null, ISafetyNetHelper.class, dexPath.getPath(), getActivity(), + (ISafetyNetHelper.Callback) code -> + MagiskManager.get().safetyNetDone.publish(false, code)); if (helper.getVersion() != Const.SNET_VER) { throw new Exception(); } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java b/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java index 7b7f307db..ecc127b1d 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java @@ -1,9 +1,6 @@ package com.topjohnwu.magisk.components; -import android.content.res.AssetManager; -import android.content.res.Resources; import android.os.Bundle; -import android.support.annotation.Keep; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.v7.app.AppCompatActivity; @@ -15,10 +12,6 @@ import com.topjohnwu.magisk.utils.Topic; public abstract class FlavorActivity extends AppCompatActivity { - private AssetManager swappedAssetManager = null; - private Resources swappedResources = null; - private Resources.Theme backupTheme = null; - @StyleRes public int getDarkTheme() { return -1; @@ -61,48 +54,4 @@ public abstract class FlavorActivity extends AppCompatActivity { setFinishOnTouchOutside(true); } } - - @Override - public Resources.Theme getTheme() { - return backupTheme == null ? super.getTheme() : backupTheme; - } - - @Override - public AssetManager getAssets() { - return swappedAssetManager == null ? super.getAssets() : swappedAssetManager; - } - - private AssetManager getAssets(String apk) { - try { - AssetManager asset = AssetManager.class.newInstance(); - AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk); - return asset; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - public Resources getResources() { - return swappedResources == null ? super.getResources() : swappedResources; - } - - @Keep - public void swapResources(String dexPath) { - AssetManager asset = getAssets(dexPath); - if (asset != null) { - backupTheme = super.getTheme(); - Resources res = super.getResources(); - swappedResources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration()); - swappedAssetManager = asset; - } - } - - @Keep - public void restoreResources() { - swappedAssetManager = null; - swappedResources = null; - backupTheme = null; - } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Const.java b/app/src/main/java/com/topjohnwu/magisk/utils/Const.java index 49afb20af..122a1d3d4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Const.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Const.java @@ -39,7 +39,7 @@ public class Const { // Versions public static final int UPDATE_SERVICE_VER = 1; - public static final int SNET_VER = 8; + public static final int SNET_VER = 9; public static int MIN_MODULE_VER() { return MagiskManager.get().magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400; @@ -81,7 +81,7 @@ public class Const { public static class Url { public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json"; public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json"; - public static final String SNET_URL = "https://github.com/topjohnwu/magisk_files/raw/727aa3a8642bf5f0982e5ea89b3f818bd783d5a2/snet.apk"; + public static final String SNET_URL = "https://github.com/topjohnwu/magisk_files/raw/fc819e3974e96d0e4430a2957df4410971ebd6f3/snet.apk"; public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d"; public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"; public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"; diff --git a/snet/build.gradle b/snet/build.gradle index a62b1bc36..23a484182 100644 --- a/snet/build.gradle +++ b/snet/build.gradle @@ -22,6 +22,6 @@ android { } dependencies { - compileOnly fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.google.android.gms:play-services-safetynet:7.0.0' /* The oldest version */ } diff --git a/snet/proguard-rules.pro b/snet/proguard-rules.pro index 56bba17b4..8cc07d286 100644 --- a/snet/proguard-rules.pro +++ b/snet/proguard-rules.pro @@ -20,6 +20,6 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class com.topjohnwu.snet.SafetyNet* { *; } +-keep class com.topjohnwu.snet.* { *; } -dontwarn java.lang.invoke** -dontwarn com.google.android.gms.common.GooglePlayServicesUtil** diff --git a/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java b/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java new file mode 100644 index 000000000..918c68661 --- /dev/null +++ b/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java @@ -0,0 +1,145 @@ +package com.topjohnwu.snet; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.util.Log; + +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.internal.zzg; +import com.google.android.gms.internal.zzlu; + +/* Decompiled and modified from GooglePlayServiceUtil.class */ +public class ModdedGPSUtil { + + private static final String TAG = "GooglePlayServicesUtil"; + static String dexPath; + + static Dialog getErrorDialog(int errCode, Activity activity, int requestCode) { + SwapResContext ctx = new SwapResContext(activity, dexPath); + Resources res = ctx.getResources(); + if (zzlu.zzQ(ctx) && errCode == 2) { + errCode = 42; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + builder.setMessage(GooglePlayServicesUtil.zze(ctx, errCode)); + + String btnMsg = GooglePlayServicesUtil.zzf(ctx, errCode); + if (btnMsg != null) { + Intent intent = GooglePlayServicesUtil.zzan(errCode); + builder.setPositiveButton(btnMsg, new zzg(activity, intent, requestCode)); + } + + switch(errCode) { + case 0: + return null; + case 1: + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_install_title)).create(); + case 2: + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_update_title)).create(); + case 3: + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_enable_title)).create(); + case 4: + case 6: + return builder.create(); + case 5: + Log.e(TAG, "An invalid account was specified when connecting. Please provide a valid account."); + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_invalid_account_title)).create(); + case 7: + Log.e(TAG, "Network error occurred. Please retry request later."); + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_network_error_title)).create(); + case 8: + Log.e(TAG, "Internal error occurred. Please see logs for detailed information"); + return builder.create(); + case 9: + Log.e(TAG, "Google Play services is invalid. Cannot recover."); + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_unsupported_title)).create(); + case 10: + Log.e(TAG, "Developer error occurred. Please see logs for detailed information"); + return builder.create(); + case 11: + Log.e(TAG, "The application is not licensed to the user."); + return builder.create(); + case 12: + case 13: + case 14: + case 15: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + case 40: + case 41: + default: + Log.e(TAG, "Unexpected error code " + errCode); + return builder.create(); + case 16: + Log.e(TAG, "One of the API components you attempted to connect to is not available."); + return builder.create(); + case 17: + Log.e(TAG, "The specified account could not be signed in."); + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_sign_in_failed_title)).create(); + case 42: + return builder.setTitle(res.getString(com.google.android.gms.R.string.common_android_wear_update_title)).create(); + } + } + + public static class SwapResContext extends ContextWrapper { + + private AssetManager asset; + private Resources resources; + + public SwapResContext(Context base, String apk) { + super(base); + asset = getAssets(apk); + Resources res = base.getResources(); + resources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration()); + } + + private AssetManager getAssets(String apk) { + try { + AssetManager asset = AssetManager.class.newInstance(); + AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk); + return asset; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public AssetManager getAssets() { + return asset; + } + } +} diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java index 70f46ac32..777d0a496 100644 --- a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java @@ -1,5 +1,6 @@ package com.topjohnwu.snet; +import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -11,16 +12,15 @@ import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.safetynet.SafetyNet; import com.google.android.gms.safetynet.SafetyNetApi; -import com.topjohnwu.magisk.asyncs.CheckSafetyNet; -import com.topjohnwu.magisk.components.Activity; -import com.topjohnwu.magisk.utils.ISafetyNetHelper; import org.json.JSONException; import org.json.JSONObject; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import java.security.SecureRandom; -public class SafetyNetHelper implements ISafetyNetHelper, GoogleApiClient.ConnectionCallbacks, +public class SafetyNetHelper implements InvocationHandler, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback { public static final int CAUSE_SERVICE_DISCONNECTED = 0x01; @@ -31,25 +31,24 @@ public class SafetyNetHelper implements ISafetyNetHelper, GoogleApiClient.Connec public static final int BASIC_PASS = 0x10; public static final int CTS_PASS = 0x20; - public static final int SNET_EXT_VER = 8; + public static final int SNET_EXT_VER = 9; private GoogleApiClient mGoogleApiClient; private Activity mActivity; - private Callback callback; + private Object callback; - @Override - public int getVersion() { - return SNET_EXT_VER; - } - - public SafetyNetHelper(Activity activity, Callback cb) { + SafetyNetHelper(Activity activity, Object cb) { mActivity = activity; callback = cb; } - // Entry point to start test - @Override - public void attest() { + /* Override ISafetyNetHelper.getVersion */ + private int getVersion() { + return SNET_EXT_VER; + } + + /* Override ISafetyNetHelper.attest */ + private void attest() { // Connect Google Service mGoogleApiClient = new GoogleApiClient.Builder(mActivity) .addApi(SafetyNet.API) @@ -59,17 +58,33 @@ public class SafetyNetHelper implements ISafetyNetHelper, GoogleApiClient.Connec mGoogleApiClient.connect(); } + @Override + public Object invoke(Object o, Method method, Object[] args) { + if (method.getName().equals("attest")) { + attest(); + } else if (method.getName().equals("getVersion")) { + return getVersion(); + } + return null; + } + + private void invokeCallback(int code) { + Class clazz = callback.getClass(); + try { + clazz.getMethod("onResponse", int.class).invoke(callback, code); + } catch (Exception ignored) {} + } + @Override public void onConnectionSuspended(int i) { - callback.onResponse(i); + invokeCallback(i); } @Override public void onConnectionFailed(@NonNull ConnectionResult result) { - mActivity.swapResources(CheckSafetyNet.dexPath.getPath()); - GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), mActivity, 0).show(); - mActivity.restoreResources(); - callback.onResponse(CONNECTION_FAIL); + if (GooglePlayServicesUtil.isUserRecoverableError(result.getErrorCode())) + ModdedGPSUtil.getErrorDialog(result.getErrorCode(), mActivity, 0).show(); + invokeCallback(CONNECTION_FAIL); } @Override @@ -101,6 +116,6 @@ public class SafetyNetHelper implements ISafetyNetHelper, GoogleApiClient.Connec mGoogleApiClient.disconnect(); // Return results - callback.onResponse(code); + invokeCallback(code); } } diff --git a/snet/src/main/java/com/topjohnwu/snet/Snet.java b/snet/src/main/java/com/topjohnwu/snet/Snet.java new file mode 100644 index 000000000..44438cb32 --- /dev/null +++ b/snet/src/main/java/com/topjohnwu/snet/Snet.java @@ -0,0 +1,13 @@ +package com.topjohnwu.snet; + +import android.app.Activity; + +import java.lang.reflect.Proxy; + +public class Snet { + public static Object newHelper(Class clazz, String dexPath, Activity activity, Object cb) { + ModdedGPSUtil.dexPath = dexPath; + return Proxy.newProxyInstance(SafetyNetHelper.class.getClassLoader(), + new Class[] { clazz }, new SafetyNetHelper(activity, cb)); + } +}