mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-11 06:32:19 +00:00
Application Component Granularity MagiskHide
Before switching to the new MagiskHide implementation (APK inotify), logcat parsing provides us lots of information to target a process. We were targeting components so that apps with multi-processes can still be hidden properly. After switching to the new implementation, our granularity is limited to the UID of the process. This is especially dangerous since Android allow apps signed with the same signature to share UIDs, and many system apps utilize this for elevated permissions for some services. This commit introduces process name matching. We could not blanketly target an UID, so the workaround is to verify its process name before unmounting. The tricky thing is that any app developer is allowed to name the process of its component to whatever they want; there is no 'one rule to catch them all' to target a specific package. As a result, Magisk Manager is updated to scan through all components of all apps, and show different processes of the same app, each as a separate hide target in the list. The hide target database also has to be updated accordingly. Each hide target is now a <package name, process name> pair. The magiskhide CLI and Magisk Manager is updated to support this new target format.
This commit is contained in:
@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
@@ -12,7 +13,8 @@ import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
@@ -20,34 +22,40 @@ import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.collection.ArraySet;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private static PackageInfo PLATFORM;
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
private static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
App.self.getPackageName(),
|
||||
"android",
|
||||
"com.android.chrome"
|
||||
);
|
||||
private static final String SAFETYNET_PROCESS = "com.google.android.gms.unstable";
|
||||
private static final String GMS_PACKAGE = "com.google.android.gms";
|
||||
|
||||
private List<PackageInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private List<HideAppInfo> fullList, showList;
|
||||
private List<HideTarget> hideList;
|
||||
private PackageManager pm;
|
||||
private boolean showSystem;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
showList = Collections.emptyList();
|
||||
fullList = new ArrayList<>();
|
||||
hideList = new ArrayList<>();
|
||||
pm = context.getPackageManager();
|
||||
showSystem = false;
|
||||
if (PLATFORM == null) {
|
||||
try {
|
||||
PLATFORM = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@@ -58,32 +66,42 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
private void addProcesses(Set<String> set, ComponentInfo[] infos) {
|
||||
if (infos != null)
|
||||
for (ComponentInfo info : infos)
|
||||
set.add(info.processName);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
PackageInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) ||
|
||||
// Get package info with all components
|
||||
List<PackageInfo> installed = pm.getInstalledPackages(
|
||||
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES |
|
||||
PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS);
|
||||
|
||||
fullList.clear();
|
||||
hideList.clear();
|
||||
|
||||
for (String line : Shell.su("magiskhide --ls").exec().getOut())
|
||||
hideList.add(new HideTarget(line));
|
||||
|
||||
for (PackageInfo pkg : installed) {
|
||||
if (!HIDE_BLACKLIST.contains(pkg.packageName) &&
|
||||
/* Do not show disabled apps */
|
||||
!info.applicationInfo.enabled ||
|
||||
/* Never show platform apps */
|
||||
PLATFORM.signatures[0].equals(info.signatures[0])) {
|
||||
i.remove();
|
||||
pkg.applicationInfo.enabled) {
|
||||
// Add all possible process names
|
||||
Set<String> procSet = new ArraySet<>();
|
||||
addProcesses(procSet, pkg.activities);
|
||||
addProcesses(procSet, pkg.services);
|
||||
addProcesses(procSet, pkg.receivers);
|
||||
addProcesses(procSet, pkg.providers);
|
||||
for (String proc : procSet) {
|
||||
fullList.add(new HideAppInfo(pkg.applicationInfo, proc));
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return Utils.getAppLabel(a.applicationInfo, pm)
|
||||
.compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm));
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
Collections.sort(fullList);
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
@@ -93,23 +111,35 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position).applicationInfo;
|
||||
HideAppInfo target = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
holder.appIcon.setImageDrawable(target.info.loadIcon(pm));
|
||||
holder.appName.setText(target.name);
|
||||
holder.process.setText(target.process);
|
||||
if (!target.info.packageName.equals(target.process)) {
|
||||
holder.appPackage.setVisibility(View.VISIBLE);
|
||||
holder.appPackage.setText("(" + target.info.packageName + ")");
|
||||
} else {
|
||||
holder.appPackage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.su("magiskhide --add " + info.packageName).submit();
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.su("magiskhide --rm " + info.packageName).submit();
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
holder.checkBox.setChecked(target.hidden);
|
||||
if (target.process.equals(SAFETYNET_PROCESS)) {
|
||||
// Do not allow user to not hide SafetyNet
|
||||
holder.checkBox.setOnCheckedChangeListener((v, c) -> holder.checkBox.setChecked(true));
|
||||
} else {
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
String pair = Utils.fmt("%s %s", target.info.packageName, target.process);
|
||||
if (isChecked) {
|
||||
Shell.su("magiskhide --add " + pair).submit();
|
||||
target.hidden = true;
|
||||
} else {
|
||||
Shell.su("magiskhide --rm " + pair).submit();
|
||||
target.hidden = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,36 +147,36 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
return showList.size();
|
||||
}
|
||||
|
||||
// True if not system app and have launch intent, or user already hidden it
|
||||
private boolean systemFilter(HideAppInfo target) {
|
||||
return showSystem || target.hidden ||
|
||||
((target.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
|
||||
pm.getLaunchIntentForPackage(target.info.packageName) != null);
|
||||
}
|
||||
|
||||
private boolean contains(String s, String filter) {
|
||||
return s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
// Show if have launch intent or not system app
|
||||
private boolean systemFilter(PackageInfo info) {
|
||||
if (showSystem)
|
||||
private boolean nameFilter(HideAppInfo target, String filter) {
|
||||
if (filter == null || filter.isEmpty())
|
||||
return true;
|
||||
return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
|
||||
pm.getLaunchIntentForPackage(info.packageName) != null;
|
||||
filter = filter.toLowerCase();
|
||||
return contains(target.name, filter) ||
|
||||
contains(target.process, filter) ||
|
||||
contains(target.info.packageName, filter);
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
showList = new ArrayList<>();
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
for (PackageInfo info : fullList) {
|
||||
if (systemFilter(info))
|
||||
showList.add(info);
|
||||
}
|
||||
} else {
|
||||
String filter = constraint.toLowerCase();
|
||||
for (PackageInfo info : fullList) {
|
||||
if ((contains(Utils.getAppLabel(info.applicationInfo, pm), filter) ||
|
||||
contains(info.packageName, filter)) && systemFilter(info)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiThreadHandler.run(this::notifyDataSetChanged);
|
||||
List<HideAppInfo> newList = new ArrayList<>();
|
||||
for (HideAppInfo target : fullList)
|
||||
if (systemFilter(target) && nameFilter(target, constraint))
|
||||
newList.add(target);
|
||||
UiThreadHandler.run(() -> {
|
||||
showList = newList;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,6 +188,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.process) TextView process;
|
||||
@BindView(R.id.package_name) TextView appPackage;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
|
||||
@@ -166,4 +197,49 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
class HideAppInfo implements Comparable<HideAppInfo> {
|
||||
String process;
|
||||
String name;
|
||||
ApplicationInfo info;
|
||||
boolean hidden;
|
||||
|
||||
HideAppInfo(ApplicationInfo info, String process) {
|
||||
this.process = process;
|
||||
this.info = info;
|
||||
name = Utils.getAppLabel(info, pm);
|
||||
for (HideTarget tgt : hideList) {
|
||||
if (tgt.process.equals(process)) {
|
||||
hidden = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(HideAppInfo o) {
|
||||
return Comparator.comparing((HideAppInfo t) -> t.hidden)
|
||||
.reversed()
|
||||
.thenComparing((a, b) -> a.name.compareToIgnoreCase(b.name))
|
||||
.thenComparing(t -> t.info.packageName)
|
||||
.thenComparing(t -> t.process)
|
||||
.compare(this, o);
|
||||
}
|
||||
}
|
||||
|
||||
class HideTarget {
|
||||
String pkg;
|
||||
String process;
|
||||
|
||||
HideTarget(String line) {
|
||||
String[] split = line.split("\\|");
|
||||
pkg = split[0];
|
||||
if (split.length >= 2) {
|
||||
process = split[1];
|
||||
} else {
|
||||
// Backwards compatibility
|
||||
process = pkg.equals(GMS_PACKAGE) ? SAFETYNET_PROCESS : pkg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,7 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
boolean showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
menu.findItem(R.id.show_system).setChecked(showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
menu.findItem(R.id.show_system).setChecked(Config.get(Config.Key.SHOW_SYSTEM_APP));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,15 +42,30 @@
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintBottom_toTopOf="@+id/process"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/process"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
@@ -59,7 +74,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/process" />
|
||||
|
||||
|
||||
<CheckBox
|
||||
|
||||
Reference in New Issue
Block a user