Repackage Magisk Manager for hiding

This commit is contained in:
topjohnwu
2017-10-21 22:54:47 +08:00
parent 5880d4a6ec
commit f5cc2af5d0
39 changed files with 127 additions and 215 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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