Add hide Magisk Manager feature

This commit is contained in:
topjohnwu 2017-08-22 03:01:54 +08:00
parent ea6552615d
commit 657f4ab303
24 changed files with 296 additions and 14 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ app/.externalNativeBuild/
*.sh
public.certificate.x509.pem
private.key.pk8
*.apk

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Void, Void, Boolean> {
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<String> 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);
}
}

View File

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

View File

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

View File

@ -136,6 +136,8 @@
<string name="manager_download_install">Press to download and install</string>
<string name="magisk_updates">Magisk Updates</string>
<string name="flashing">Flashing</string>
<string name="hide_manager_toast">Hiding Magisk Manager…</string>
<string name="hide_manager_fail_toast">Hide Magisk Manager failed…</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>
@ -145,6 +147,8 @@
<string name="settings_notification_summary">Show update notifications when new version is available</string>
<string name="settings_clear_cache_title">Clear Repo Cache</string>
<string name="settings_clear_cache_summary">Clear the cached information for online repos, forces the app to refresh online</string>
<string name="settings_hide_manager_title">Hide Magisk Manager</string>
<string name="settings_hide_manager_summary">Temporarily hide Magisk Manager.\nThis will install a new app called \"Unhide Magisk Manager\"</string>
<string name="language">Language</string>
<string name="system_default">(System Default)</string>

View File

@ -20,6 +20,11 @@
android:title="@string/settings_clear_cache_title"
android:summary="@string/settings_clear_cache_summary" />
<Preference
android:key="hide"
android:title="@string/settings_hide_manager_title"
android:summary="@string/settings_hide_manager_summary" />
</PreferenceCategory>
<PreferenceCategory

1
resource/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

23
resource/build.gradle Normal file
View File

@ -0,0 +1,23 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -0,0 +1 @@
<manifest package="com.topjohnwu.resource" />

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1 +1 @@
include ':app'
include ':app', ':unhide', ':resource'

1
unhide/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

25
unhide/build.gradle Normal file
View File

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

25
unhide/proguard-rules.pro vendored Normal file
View File

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

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.topjohnwu.unhide">
<application
android:icon="@mipmap/ic_launcher"
android:label="Unhide Magisk Manager"
android:theme="@android:style/Theme.NoDisplay">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

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