mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-06-12 13:17:39 +02:00
Repackage Magisk Manager for hiding
This commit is contained in:
@ -9,6 +9,7 @@ import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
@ -44,6 +45,7 @@ public class MagiskManager extends Application {
|
||||
// Global weak reference to self
|
||||
private static WeakReference<MagiskManager> weakSelf;
|
||||
|
||||
public static final String ORIG_PKG_NAME = "com.topjohnwu.magisk";
|
||||
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
||||
public static final String MAGISK_HOST_FILE = "/magisk/.core/hosts";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
@ -68,6 +70,7 @@ public class MagiskManager extends Application {
|
||||
public final Topic localeDone = new Topic();
|
||||
|
||||
// Info
|
||||
public int userId;
|
||||
public String magiskVersionString;
|
||||
public int magiskVersionCode = -1;
|
||||
public String remoteMagiskVersionString;
|
||||
@ -125,6 +128,7 @@ public class MagiskManager extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
userId = getApplicationInfo().uid / 100000;
|
||||
|
||||
if (Utils.getDatabasePath(this, SuDatabaseHelper.DB_NAME).exists()) {
|
||||
// Don't migrate yet, wait and check Magisk version
|
||||
@ -133,6 +137,15 @@ public class MagiskManager extends Application {
|
||||
suDB = new SuDatabaseHelper();
|
||||
}
|
||||
|
||||
// If detect original package, self destruct!
|
||||
if (!getPackageName().equals(ORIG_PKG_NAME)) {
|
||||
try {
|
||||
getPackageManager().getApplicationInfo(ORIG_PKG_NAME, 0);
|
||||
Shell.su(String.format(Locale.US, "pm uninstall --user %d %s", userId, getPackageName()));
|
||||
return;
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Expected*/ }
|
||||
}
|
||||
|
||||
repoDB = new RepoDatabaseHelper(this);
|
||||
defaultLocale = Locale.getDefault();
|
||||
setLocale();
|
||||
@ -258,6 +271,14 @@ public class MagiskManager extends Application {
|
||||
// Setup suDB
|
||||
SuDatabaseHelper.setupSuDB();
|
||||
|
||||
// Check alternative Magisk Manager
|
||||
String pkg;
|
||||
if (getPackageName().equals(ORIG_PKG_NAME) &&
|
||||
(pkg = suDB.getStrings(SuDatabaseHelper.REQUESTER, null)) != null) {
|
||||
Shell.su_raw("pm uninstall " + pkg);
|
||||
suDB.setStrings(SuDatabaseHelper.REQUESTER, null);
|
||||
}
|
||||
|
||||
// Add update checking service
|
||||
if (UPDATE_SERVICE_VER > updateServiceVersion) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
|
@ -108,7 +108,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in user mode if selected owner manage
|
||||
if (getActivity().getApplicationInfo().uid > 99999) {
|
||||
if (mm.userId > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
@ -125,12 +125,16 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
return true;
|
||||
});
|
||||
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> new HideManager().exec());
|
||||
return true;
|
||||
});
|
||||
if (mm.getPackageName().equals(MagiskManager.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> new HideManager().exec());
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
|
@ -1,27 +1,26 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.jarsigner.JarMap;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private static final String UNHIDE_APK = "unhide.apk";
|
||||
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes();
|
||||
|
||||
private String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
@ -44,9 +43,33 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private boolean findAndPatch(byte xml[], String from, String to) {
|
||||
int offset = -1;
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
for (int i = 0; i < xml.length - target.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < target.length; ++j) {
|
||||
if (xml[i + j] != target[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (offset < 0)
|
||||
return false;
|
||||
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
MagiskManager.toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT);
|
||||
MagiskManager.toast(R.string.hide_manager_toast2, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -54,67 +77,43 @@ public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
// Generate a new unhide app with random package name
|
||||
File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk");
|
||||
unhideAPK.getParentFile().mkdirs();
|
||||
String pkg;
|
||||
File repack = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "repack.apk");
|
||||
repack.getParentFile().mkdirs();
|
||||
String pkg = genPackageName("com.", MagiskManager.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
JarMap asset = new JarMap(mm.getAssets().open(UNHIDE_APK));
|
||||
// Read whole APK into memory
|
||||
JarMap apk = new JarMap(new FileInputStream(mm.getPackageCodePath()));
|
||||
JarEntry je = new JarEntry(ANDROID_MANIFEST);
|
||||
byte xml[] = asset.getRawData(je);
|
||||
int offset = -1;
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
// Linear search pattern offset
|
||||
for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) {
|
||||
if (xml[i + j] != UNHIDE_PKG_NAME[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (offset < 0)
|
||||
if (!findAndPatch(xml, MagiskManager.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
if (!findAndPatch(xml, MagiskManager.ORIG_PKG_NAME + ".provider", pkg + ".provider"))
|
||||
return false;
|
||||
|
||||
// Patch binary XML with new package name
|
||||
pkg = genPackageName("com.", UNHIDE_PKG_NAME.length - 1);
|
||||
System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length());
|
||||
asset.getOutputStream(je).write(xml);
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
|
||||
// Sign the APK
|
||||
ZipUtils.signZip(mm, asset, unhideAPK, false);
|
||||
ZipUtils.signZip(apk, repack, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
List<String> ret = Shell.su("pm install " + unhideAPK + ">/dev/null && echo true || echo false");
|
||||
unhideAPK.delete();
|
||||
|
||||
List<String> ret = Shell.su(String.format(Locale.US,
|
||||
"pm install --user %d %s >/dev/null && echo true || echo false",
|
||||
mm.userId, repack));
|
||||
repack.delete();
|
||||
if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0)))
|
||||
return false;
|
||||
|
||||
try {
|
||||
// Allow the application to gain root by default
|
||||
PackageManager pm = mm.getPackageManager();
|
||||
int uid = pm.getApplicationInfo(pkg, 0).uid;
|
||||
Policy policy = new Policy(uid, pm);
|
||||
policy.policy = Policy.ALLOW;
|
||||
policy.notification = false;
|
||||
policy.logging = false;
|
||||
mm.suDB.addPolicy(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
mm.suDB.setStrings(SuDatabaseHelper.REQUESTER, pkg);
|
||||
Shell.su_raw(String.format(Locale.US, "pm uninstall --user %d %s", mm.userId, mm.getPackageName()));
|
||||
|
||||
// Hide myself!
|
||||
Shell.su_raw("pm hide " + mm.getPackageName());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -132,13 +132,13 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
publishProgress(SHOW_PROCESSING);
|
||||
|
||||
// Then sign the zip for the first time, temp1 -> temp2
|
||||
ZipUtils.signZip(activity, temp1, temp2, false);
|
||||
ZipUtils.signZip(temp1, temp2, false);
|
||||
|
||||
// Adjust the zip to prevent unzip issues, temp2 -> temp1
|
||||
ZipUtils.zipAdjust(temp2.getPath(), temp1.getPath());
|
||||
|
||||
// Finally, sign the whole zip file again, temp1 -> temp2
|
||||
ZipUtils.signZip(activity, temp1, temp2, true);
|
||||
ZipUtils.signZip(temp1, temp2, true);
|
||||
|
||||
// Write it to the target zip, temp2 -> file
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(mFile));
|
||||
|
@ -41,11 +41,14 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
|
||||
public static final String REQUESTER = "requester";
|
||||
|
||||
public static final String DB_NAME = "su.db";
|
||||
private static final int DATABASE_VER = 3;
|
||||
private static final int DATABASE_VER = 4;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
private static final String STRINGS_TABLE = "strings";
|
||||
|
||||
private static String GLOBAL_DB;
|
||||
|
||||
@ -130,7 +133,7 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 2;
|
||||
oldVersion = 3;
|
||||
}
|
||||
if (oldVersion == 1) {
|
||||
// We're dropping column app_name, rename and re-construct table
|
||||
@ -157,6 +160,12 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
db.execSQL("UPDATE " + LOG_TABLE + " SET time=time*1000");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + STRINGS_TABLE + " " +
|
||||
"(key TEXT, value TEXT, PRIMARY KEY(key))");
|
||||
++oldVersion;
|
||||
}
|
||||
|
||||
if (!Utils.itemExist(GLOBAL_DB)) {
|
||||
// Hard link our DB globally
|
||||
@ -335,4 +344,25 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
mDb.delete(STRINGS_TABLE, "key=?", new String[] { key });
|
||||
} else {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
mDb.replace(STRINGS_TABLE, null, data);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try (Cursor c = mDb.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public class ManagerUpdate extends BroadcastReceiver {
|
||||
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Uri content = FileProvider.getUriForFile(context,
|
||||
"com.topjohnwu.magisk.provider", new File(uri.getPath()));
|
||||
context.getPackageName() + ".provider", new File(uri.getPath()));
|
||||
install.setData(content);
|
||||
context.startActivity(install);
|
||||
} else {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.topjohnwu.jarsigner.JarMap;
|
||||
import com.topjohnwu.jarsigner.SignAPK;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
@ -63,16 +63,16 @@ public class ZipUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(Context context, InputStream is, File output, boolean minSign) throws Exception {
|
||||
signZip(context, new JarMap(is, false), output, minSign);
|
||||
public static void signZip(InputStream is, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(is, false), output, minSign);
|
||||
}
|
||||
|
||||
public static void signZip(Context context, File input, File output, boolean minSign) throws Exception {
|
||||
signZip(context, new JarMap(input, false), output, minSign);
|
||||
public static void signZip(File input, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(input, false), output, minSign);
|
||||
}
|
||||
|
||||
public static void signZip(Context context, JarMap input, File output, boolean minSign) throws Exception {
|
||||
AssetManager assets = context.getAssets();
|
||||
public static void signZip(JarMap input, File output, boolean minSign) throws Exception {
|
||||
AssetManager assets = MagiskManager.get().getAssets();
|
||||
SignAPK.signZip(
|
||||
assets.open(PUBLIC_KEY_NAME), assets.open(PRIVATE_KEY_NAME),
|
||||
input, output, minSign);
|
||||
|
Reference in New Issue
Block a user