diff --git a/.gitignore b/.gitignore index 48c2b5409..f0b6408bf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ app/.externalNativeBuild/ *.sh public.certificate.x509.pem private.key.pk8 +*.apk diff --git a/app/build.gradle b/app/build.gradle index 9b75ff410..816286f46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,10 +53,11 @@ repositories { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:recyclerview-v7:26.0.0' - implementation 'com.android.support:cardview-v7:26.0.0' - implementation 'com.android.support:design:26.0.0' - implementation 'com.android.support:support-v4:26.0.0' + implementation project(':resource') + implementation 'com.android.support:recyclerview-v7:26.0.1' + implementation 'com.android.support:cardview-v7:26.0.1' + implementation 'com.android.support:design:26.0.1' + implementation 'com.android.support:support-v4:26.0.1' implementation 'com.jakewharton:butterknife:8.8.1' implementation 'com.atlassian.commonmark:commonmark:0.9.0' implementation 'org.bouncycastle:bcprov-jdk15on:1.57' diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java index b2a64f443..f37df6d87 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java @@ -53,6 +53,9 @@ public class MagiskFragment extends Fragment public static final String SHOW_DIALOG = "dialog"; + private static final String UNINSTALLER = "magisk_uninstaller.sh"; + private static final String UTIL_FUNCTIONS= "util_functions.sh"; + private static int expandHeight = 0; private static boolean mExpanded = false; @@ -176,8 +179,8 @@ public class MagiskFragment extends Fragment .setMessage(R.string.uninstall_magisk_msg) .setPositiveButton(R.string.yes, (dialogInterface, i) -> { try { - InputStream in = magiskManager.getAssets().open(MagiskManager.UNINSTALLER); - File uninstaller = new File(magiskManager.getCacheDir(), MagiskManager.UNINSTALLER); + InputStream in = magiskManager.getAssets().open(UNINSTALLER); + File uninstaller = new File(magiskManager.getCacheDir(), UNINSTALLER); FileOutputStream out = new FileOutputStream(uninstaller); byte[] bytes = new byte[1024]; int read; @@ -186,8 +189,8 @@ public class MagiskFragment extends Fragment } in.close(); out.close(); - in = magiskManager.getAssets().open(MagiskManager.UTIL_FUNCTIONS); - File utils = new File(magiskManager.getCacheDir(), MagiskManager.UTIL_FUNCTIONS); + in = magiskManager.getAssets().open(UTIL_FUNCTIONS); + File utils = new File(magiskManager.getCacheDir(), UTIL_FUNCTIONS); out = new FileOutputStream(utils); while ((read = in.read(bytes)) != -1) { out.write(bytes, 0, read); @@ -207,8 +210,8 @@ public class MagiskFragment extends Fragment public void onFinish() { progress.setMessage(getString(R.string.reboot_countdown, 0)); Shell.getShell(getActivity()).su_raw( - "mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER, - "mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS, + "mv -f " + uninstaller + " /cache/" + UNINSTALLER, + "mv -f " + utils + " /data/magisk/" + UTIL_FUNCTIONS, "reboot" ); } diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java index 219bf8c1d..438e6c1fd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java @@ -32,8 +32,6 @@ public class MagiskManager extends Application { public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk"; public static final String TMP_FOLDER_PATH = "/dev/tmp"; public static final String MAGISK_PATH = "/magisk"; - public static final String UNINSTALLER = "magisk_uninstaller.sh"; - public static final String UTIL_FUNCTIONS= "util_functions.sh"; public static final String INTENT_SECTION = "section"; public static final String INTENT_VERSION = "version"; public static final String INTENT_LINK = "link"; diff --git a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index e435bc81d..d4baae8cd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -4,6 +4,7 @@ import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.preference.ListPreference; +import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; @@ -13,6 +14,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.widget.Toast; +import com.topjohnwu.magisk.asyncs.HideManager; import com.topjohnwu.magisk.components.Activity; import com.topjohnwu.magisk.database.SuDatabaseHelper; import com.topjohnwu.magisk.utils.Logger; @@ -98,6 +100,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { multiuserMode = (ListPreference) findPreference("multiuser_mode"); namespaceMode = (ListPreference) findPreference("mnt_ns"); SwitchPreference reauth = (SwitchPreference) findPreference("su_reauth"); + Preference hideManager = findPreference("hide"); setSummary(); @@ -105,6 +108,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { if (getActivity().getApplicationInfo().uid > 99999) { prefScreen.removePreference(magiskCategory); prefScreen.removePreference(suCategory); + generalCatagory.removePreference(hideManager); } // Remove re-authentication option on Android O, it will not work @@ -117,6 +121,11 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { return true; }); + hideManager.setOnPreferenceClickListener((pref) -> { + new HideManager(getActivity()).exec(); + return true; + }); + if (!BuildConfig.DEBUG) { prefScreen.removePreference(developer); } diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java new file mode 100644 index 000000000..2fe77161c --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java @@ -0,0 +1,72 @@ +package com.topjohnwu.magisk.asyncs; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.widget.Toast; + +import com.topjohnwu.magisk.MagiskManager; +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.superuser.Policy; +import com.topjohnwu.magisk.utils.Utils; +import com.topjohnwu.magisk.utils.ZipUtils; + +import java.io.File; +import java.util.List; + +public class HideManager extends ParallelTask { + + public HideManager(Context context) { + super(context); + } + + @Override + protected void onPreExecute() { + getMagiskManager().toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT); + } + + @Override + protected Boolean doInBackground(Void... voids) { + MagiskManager magiskManager = getMagiskManager(); + if (magiskManager == null) + return false; + + // Generate a new unhide app with random package name + File unhideAPK = new File(magiskManager.getCacheDir(), "unhide.apk"); + String pkg = ZipUtils.generateUnhide(magiskManager, unhideAPK); + + // Install the application + List ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false"); + unhideAPK.delete(); + if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0))) + return false; + + try { + // Allow the application to gain root by default + PackageManager pm = magiskManager.getPackageManager(); + int uid = pm.getApplicationInfo(pkg, 0).uid; + Policy policy = new Policy(uid, pm); + policy.policy = Policy.ALLOW; + policy.notification = false; + policy.logging = false; + magiskManager.suDB.addPolicy(policy); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + + // Hide myself! + getShell().su_raw("pm hide " + magiskManager.getPackageName()); + return true; + } + + @Override + protected void onPostExecute(Boolean b) { + MagiskManager magiskManager = getMagiskManager(); + if (magiskManager == null) + return; + if (!b) { + magiskManager.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG); + } + super.onPostExecute(b); + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index 61e4d854e..a586d7f52 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -35,6 +35,7 @@ import com.topjohnwu.magisk.receivers.DownloadReceiver; import com.topjohnwu.magisk.receivers.ManagerUpdate; import java.io.File; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -288,4 +289,25 @@ public class Utils { return locales; } + + public static String genPackageName(String prefix, int length) { + StringBuilder builder = new StringBuilder(length); + builder.append(prefix); + length -= prefix.length(); + SecureRandom random = new SecureRandom(); + String base = "abcdefghijklmnopqrstuvwxyz"; + String alpha = base + base.toUpperCase(); + String full = alpha + "0123456789.........."; + char next, prev = '\0'; + for (int i = 0; i < length; ++i) { + if (prev == '.' || i == length - 1 || i == 0) { + next = alpha.charAt(random.nextInt(alpha.length())); + } else { + next = full.charAt(random.nextInt(full.length())); + } + builder.append(next); + prev = next; + } + return builder.toString(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java index 7c4b57690..6fd2d5ea0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java @@ -1,6 +1,7 @@ package com.topjohnwu.magisk.utils; import android.content.Context; +import android.text.TextUtils; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; @@ -65,6 +66,7 @@ public class ZipUtils { // File name in assets private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem"; private static final String PRIVATE_KEY_NAME = "private.key.pk8"; + private static final String UNHIDE_NAME = "unhide.apk"; private static final String CERT_SF_NAME = "META-INF/CERT.SF"; private static final String CERT_SIG_NAME = "META-INF/CERT.%s"; @@ -82,6 +84,45 @@ public class ZipUtils { public native static void zipAdjust(String filenameIn, String filenameOut); + public static String generateUnhide(Context context, File output) { + File temp = new File(context.getCacheDir(), "temp.apk"); + String pkg = ""; + try { + JarInputStream source = new JarInputStream(context.getAssets().open(UNHIDE_NAME)); + JarOutputStream dest = new JarOutputStream(new FileOutputStream(temp)); + JarEntry entry; + int size; + byte buffer[] = new byte[4096]; + while ((entry = source.getNextJarEntry()) != null) { + dest.putNextEntry(new JarEntry(entry.getName())); + if (TextUtils.equals(entry.getName(), "AndroidManifest.xml")) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while((size = source.read(buffer)) != -1) { + baos.write(buffer, 0, size); + } + byte xml[] = baos.toByteArray(); + pkg = Utils.genPackageName("com.", 20); + for (int i = 0; i < 20; ++i) { + xml[424 + i] = (byte) pkg.charAt(i); + } + dest.write(xml); + } else { + while((size = source.read(buffer)) != -1) { + dest.write(buffer, 0, size); + } + } + } + source.close(); + dest.close(); + signZip(context, temp, output, false); + temp.delete(); + } catch (IOException e) { + e.printStackTrace(); + return pkg; + } + return pkg; + } + public static void removeTopFolder(InputStream in, File output) throws IOException { try { JarInputStream source = new JarInputStream(in); @@ -102,7 +143,7 @@ public class ZipUtils { continue; } dest.putNextEntry(new JarEntry(path)); - while((size = source.read(buffer, 0, 2048)) != -1) { + while((size = source.read(buffer)) != -1) { dest.write(buffer, 0, size); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2beca7516..7bad2f666 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,6 +136,8 @@ Press to download and install Magisk Updates Flashing + Hiding Magisk Manager… + Hide Magisk Manager failed… General @@ -145,6 +147,8 @@ Show update notifications when new version is available Clear Repo Cache Clear the cached information for online repos, forces the app to refresh online + Hide Magisk Manager + Temporarily hide Magisk Manager.\nThis will install a new app called \"Unhide Magisk Manager\" Language (System Default) diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index 2a0ba468a..0529547b5 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -20,6 +20,11 @@ android:title="@string/settings_clear_cache_title" android:summary="@string/settings_clear_cache_summary" /> + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/resource/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-hdpi/ic_launcher.png rename to resource/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/resource/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-mdpi/ic_launcher.png rename to resource/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/resource/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to resource/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/resource/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to resource/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/resource/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to resource/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/settings.gradle b/settings.gradle index 9d495b34f..1cf7f1e5e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' \ No newline at end of file +include ':app', ':unhide', ':resource' \ No newline at end of file diff --git a/unhide/.gitignore b/unhide/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/unhide/.gitignore @@ -0,0 +1 @@ +/build diff --git a/unhide/build.gradle b/unhide/build.gradle new file mode 100644 index 000000000..77106942a --- /dev/null +++ b/unhide/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + defaultConfig { + applicationId "com.topjohnwu.unhide" + minSdkVersion 21 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':resource') +} diff --git a/unhide/proguard-rules.pro b/unhide/proguard-rules.pro new file mode 100644 index 000000000..71961146f --- /dev/null +++ b/unhide/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/unhide/src/main/AndroidManifest.xml b/unhide/src/main/AndroidManifest.xml new file mode 100644 index 000000000..65f25fcd1 --- /dev/null +++ b/unhide/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/unhide/src/main/java/com/topjohnwu/unhide/MainActivity.java b/unhide/src/main/java/com/topjohnwu/unhide/MainActivity.java new file mode 100644 index 000000000..a63604a8b --- /dev/null +++ b/unhide/src/main/java/com/topjohnwu/unhide/MainActivity.java @@ -0,0 +1,33 @@ +package com.topjohnwu.unhide; + +import android.app.Activity; +import android.os.Bundle; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String command = String.format( + "pm unhide com.topjohnwu.magisk\n" + + "am start -n com.topjohnwu.magisk/.SplashActivity\n" + + "pm uninstall %s\n" + + "exit\n", + getApplicationInfo().packageName); + Process process; + try { + process = Runtime.getRuntime().exec("su"); + OutputStream in = process.getOutputStream(); + in.write(command.getBytes("UTF-8")); + in.flush(); + process.waitFor(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + finish(); + } +}