Preparation for hiding isolated processes
This commit is contained in:
parent
3f9a64417b
commit
8e61080a4a
@ -7,9 +7,11 @@ import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.*
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS
|
||||
import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.database.Cursor
|
||||
@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray
|
||||
|
||||
val packageName: String get() = get<Context>().packageName
|
||||
|
||||
val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() {
|
||||
val pm = get<PackageManager>()
|
||||
val appProcessName = processName ?: packageName
|
||||
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
|
||||
val packageInfo = try {
|
||||
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
|
||||
pm.getPackageInfo(packageName, baseFlag or request)
|
||||
} catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276
|
||||
return listOf(appProcessName)
|
||||
} catch (e: Exception) {
|
||||
// Exceed binder data transfer limit, fetch each component type separately
|
||||
pm.getPackageInfo(packageName, baseFlag).apply {
|
||||
runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
|
||||
runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
|
||||
runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
|
||||
runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
|
||||
}
|
||||
}
|
||||
fun Array<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName }
|
||||
return with(packageInfo) {
|
||||
activities?.processNames().orEmpty() +
|
||||
services?.processNames().orEmpty() +
|
||||
receivers?.processNames().orEmpty() +
|
||||
providers?.processNames().orEmpty()
|
||||
}
|
||||
}
|
||||
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
|
||||
|
||||
@get:SuppressLint("InlinedApi")
|
||||
val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0
|
||||
|
||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
|
||||
class HideTarget(line: String) {
|
||||
val packageName: String
|
||||
val process: String
|
||||
|
||||
init {
|
||||
val split = line.split(Regex("\\|"), 2)
|
||||
packageName = split[0]
|
||||
process = split.getOrElse(1) { packageName }
|
||||
}
|
||||
}
|
||||
|
||||
class HideAppInfo(info: ApplicationInfo, pm: PackageManager)
|
||||
: ApplicationInfo(info), Comparable<HideAppInfo> {
|
||||
|
||||
val label = info.getLabel(pm)
|
||||
val iconImage: Drawable = info.loadIcon(pm)
|
||||
|
||||
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
|
||||
|
||||
companion object {
|
||||
private val comparator = compareBy<HideAppInfo>(
|
||||
{ it.label.toLowerCase(currentLocale) },
|
||||
{ it.packageName }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class HideProcessInfo(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
class HideAppTarget(
|
||||
val info: HideAppInfo,
|
||||
val processes: List<HideProcessInfo>
|
||||
) : Comparable<HideAppTarget> {
|
||||
override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info }
|
||||
}
|
102
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
102
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
@ -0,0 +1,102 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.*
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.ktx.isIsolated
|
||||
import com.topjohnwu.magisk.ktx.useAppZygote
|
||||
|
||||
class CmdlineHiddenItem(line: String) {
|
||||
val packageName: String
|
||||
val process: String
|
||||
|
||||
init {
|
||||
val split = line.split(Regex("\\|"), 2)
|
||||
packageName = split[0]
|
||||
process = split.getOrElse(1) { packageName }
|
||||
}
|
||||
}
|
||||
|
||||
const val ISOLATED_MAGIC = "isolated"
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<CmdlineHiddenItem>)
|
||||
: ApplicationInfo(info), Comparable<HideAppInfo> {
|
||||
|
||||
val label = info.getLabel(pm)
|
||||
val iconImage: Drawable = info.loadIcon(pm)
|
||||
val processes = fetchProcesses(pm, hideList)
|
||||
|
||||
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
|
||||
|
||||
private fun fetchProcesses(
|
||||
pm: PackageManager,
|
||||
hideList: List<CmdlineHiddenItem>
|
||||
): List<HideProcessInfo> {
|
||||
// Fetch full PackageInfo
|
||||
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
|
||||
val packageInfo = try {
|
||||
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
|
||||
pm.getPackageInfo(packageName, baseFlag or request)
|
||||
} catch (e: NameNotFoundException) {
|
||||
// EdXposed hooked, issue#3276
|
||||
return emptyList()
|
||||
} catch (e: Exception) {
|
||||
// Exceed binder data transfer limit, fetch each component type separately
|
||||
pm.getPackageInfo(packageName, baseFlag).apply {
|
||||
runCatching { activities = pm.getPackageInfo(packageName, baseFlag or GET_ACTIVITIES).activities }
|
||||
runCatching { services = pm.getPackageInfo(packageName, baseFlag or GET_SERVICES).services }
|
||||
runCatching { receivers = pm.getPackageInfo(packageName, baseFlag or GET_RECEIVERS).receivers }
|
||||
runCatching { providers = pm.getPackageInfo(packageName, baseFlag or GET_PROVIDERS).providers }
|
||||
}
|
||||
}
|
||||
|
||||
val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC }
|
||||
fun createProcess(name: String, pkg: String = packageName): HideProcessInfo {
|
||||
return HideProcessInfo(name, pkg, hidden.any { it.process == name })
|
||||
}
|
||||
|
||||
var haveAppZygote = false
|
||||
fun Array<out ComponentInfo>.processes() = map { createProcess(it.processName) }
|
||||
fun Array<ServiceInfo>.processes() = map {
|
||||
if (it.isIsolated) {
|
||||
if (it.useAppZygote) {
|
||||
haveAppZygote = true
|
||||
// Using app zygote, don't need to track the process
|
||||
null
|
||||
} else {
|
||||
createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC)
|
||||
}
|
||||
} else {
|
||||
createProcess(it.processName)
|
||||
}
|
||||
}
|
||||
|
||||
return with(packageInfo) {
|
||||
activities?.processes().orEmpty() +
|
||||
services?.processes().orEmpty() +
|
||||
receivers?.processes().orEmpty() +
|
||||
providers?.processes().orEmpty() +
|
||||
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null)
|
||||
}.filterNotNull().distinctBy { it.name }.sortedBy { it.name }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val comparator = compareBy<HideAppInfo>(
|
||||
{ it.label.toLowerCase(currentLocale) },
|
||||
{ it.packageName }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class HideProcessInfo(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
var isHidden: Boolean
|
||||
)
|
@ -12,14 +12,13 @@ import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class HideItem(
|
||||
app: HideAppTarget
|
||||
) : ObservableItem<HideItem>(), Comparable<HideItem> {
|
||||
class HideRvItem(
|
||||
val info: HideAppInfo
|
||||
) : ObservableItem<HideRvItem>(), Comparable<HideRvItem> {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_md2
|
||||
override val layoutRes get() = R.layout.item_hide_md2
|
||||
|
||||
val info = app.info
|
||||
val processes = app.processes.map { HideProcessItem(it) }
|
||||
val processes = info.processes.map { HideProcessRvItem(it) }
|
||||
|
||||
@get:Bindable
|
||||
var isExpanded = false
|
||||
@ -73,10 +72,10 @@ class HideItem(
|
||||
}
|
||||
}
|
||||
|
||||
override fun compareTo(other: HideItem) = comparator.compare(this, other)
|
||||
override fun compareTo(other: HideRvItem) = comparator.compare(this, other)
|
||||
|
||||
companion object {
|
||||
private val comparator = compareBy<HideItem>(
|
||||
private val comparator = compareBy<HideRvItem>(
|
||||
{ it.itemsChecked == 0 },
|
||||
{ it.info }
|
||||
)
|
||||
@ -84,16 +83,17 @@ class HideItem(
|
||||
|
||||
}
|
||||
|
||||
class HideProcessItem(
|
||||
class HideProcessRvItem(
|
||||
val process: HideProcessInfo
|
||||
) : ObservableItem<HideProcessItem>() {
|
||||
) : ObservableItem<HideProcessRvItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_process_md2
|
||||
override val layoutRes get() = R.layout.item_hide_process_md2
|
||||
|
||||
@get:Bindable
|
||||
var isHidden = process.isHidden
|
||||
set(value) = set(value, field, { field = it }, BR.hidden) {
|
||||
val arg = if (isHidden) "add" else "rm"
|
||||
var isHidden
|
||||
get() = process.isHidden
|
||||
set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
|
||||
val arg = if (it) "add" else "rm"
|
||||
val (name, pkg) = process
|
||||
Shell.su("magiskhide --$arg $pkg $name").submit()
|
||||
}
|
||||
@ -102,7 +102,7 @@ class HideProcessItem(
|
||||
isHidden = !isHidden
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = process == other.process
|
||||
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
|
||||
override fun contentSameAs(other: HideProcessRvItem) = process == other.process
|
||||
override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.hide
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
import android.os.Process
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@ -14,7 +15,6 @@ import com.topjohnwu.magisk.arch.itemBindingOf
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.ktx.packageName
|
||||
import com.topjohnwu.magisk.ktx.processes
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@ -45,11 +45,11 @@ class HideViewModel : BaseViewModel(), Queryable {
|
||||
submitQuery()
|
||||
}
|
||||
|
||||
val items = filterableListOf<HideItem>()
|
||||
val itemBinding = itemBindingOf<HideItem> {
|
||||
val items = filterableListOf<HideRvItem>()
|
||||
val itemBinding = itemBindingOf<HideRvItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
val itemInternalBinding = itemBindingOf<HideProcessItem> {
|
||||
val itemInternalBinding = itemBindingOf<HideProcessRvItem> {
|
||||
it.bindExtra(BR.viewModel, this)
|
||||
}
|
||||
|
||||
@ -62,14 +62,13 @@ class HideViewModel : BaseViewModel(), Queryable {
|
||||
state = State.LOADING
|
||||
val (apps, diff) = withContext(Dispatchers.Default) {
|
||||
val pm = get<PackageManager>()
|
||||
val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) }
|
||||
val apps = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES)
|
||||
val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) }
|
||||
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
|
||||
.asSequence()
|
||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||
.map { HideAppInfo(it, pm) }
|
||||
.map { createTarget(it, hides) }
|
||||
.map { HideAppInfo(it, pm, hideList) }
|
||||
.filter { it.processes.isNotEmpty() }
|
||||
.map { HideItem(it) }
|
||||
.map { HideRvItem(it) }
|
||||
.toList()
|
||||
.sorted()
|
||||
apps to items.calculateDiff(apps)
|
||||
@ -80,18 +79,6 @@ class HideViewModel : BaseViewModel(), Queryable {
|
||||
|
||||
// ---
|
||||
|
||||
private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
|
||||
val pkg = info.packageName
|
||||
val hidden = hideList.filter { it.packageName == pkg }
|
||||
val processNames = info.processes.distinct()
|
||||
val processes = processNames.map { name ->
|
||||
HideProcessInfo(name, pkg, hidden.any { name == it.process })
|
||||
}
|
||||
return HideAppTarget(info, processes)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override fun query() {
|
||||
items.filter {
|
||||
fun showHidden() = it.itemsChecked != 0
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideItem" />
|
||||
type="com.topjohnwu.magisk.ui.hide.HideRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideProcessItem" />
|
||||
type="com.topjohnwu.magisk.ui.hide.HideProcessRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
|
@ -68,7 +68,7 @@ static void load_overlay_rc(const char *overlay) {
|
||||
// Do not allow overwrite init.rc
|
||||
unlinkat(dfd, "init.rc", 0);
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (strend(entry->d_name, ".rc") == 0) {
|
||||
if (str_ends(entry->d_name, ".rc")) {
|
||||
LOGD("Found rc script [%s]\n", entry->d_name);
|
||||
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
rc_list.push_back(fd_full_read(rc));
|
||||
|
@ -48,23 +48,27 @@ void set_hide_state(bool state) {
|
||||
hide_state = state;
|
||||
}
|
||||
|
||||
template <bool str_op(string_view, string_view)>
|
||||
static bool proc_name_match(int pid, const char *name) {
|
||||
char buf[4019];
|
||||
sprintf(buf, "/proc/%d/cmdline", pid);
|
||||
if (FILE *f = fopen(buf, "re")) {
|
||||
fgets(buf, sizeof(buf), f);
|
||||
fclose(f);
|
||||
if (strcmp(buf, name) == 0)
|
||||
if (auto fp = open_file(buf, "re")) {
|
||||
fgets(buf, sizeof(buf), fp.get());
|
||||
if (str_op(buf, name)) {
|
||||
LOGD("hide_utils: kill PID=[%d] (%s)\n", pid, buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void kill_process(const char *name, bool multi = false) {
|
||||
static inline bool str_eql(string_view s, string_view ss) { return s == ss; }
|
||||
|
||||
static void kill_process(const char *name, bool multi = false,
|
||||
bool (*filter)(int, const char *) = proc_name_match<&str_eql>) {
|
||||
crawl_procfs([=](int pid) -> bool {
|
||||
if (proc_name_match(pid, name)) {
|
||||
if (kill(pid, SIGTERM) == 0)
|
||||
LOGD("hide_utils: killed PID=[%d] (%s)\n", pid, name);
|
||||
if (filter(pid, name)) {
|
||||
kill(pid, SIGTERM);
|
||||
return multi;
|
||||
}
|
||||
return true;
|
||||
@ -72,6 +76,8 @@ static void kill_process(const char *name, bool multi = false) {
|
||||
}
|
||||
|
||||
static bool validate(const char *s) {
|
||||
if (strcmp(s, ISOLATED_MAGIC) == 0)
|
||||
return true;
|
||||
bool dot = false;
|
||||
for (char c; (c = *s); ++s) {
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||
@ -87,7 +93,18 @@ static bool validate(const char *s) {
|
||||
return dot;
|
||||
}
|
||||
|
||||
static int add_list(const char *pkg, const char *proc = "") {
|
||||
static void add_hide_set(const char *pkg, const char *proc) {
|
||||
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
|
||||
hide_set.emplace(pkg, proc);
|
||||
if (strcmp(pkg, ISOLATED_MAGIC) == 0) {
|
||||
// Kill all matching isolated processes
|
||||
kill_process(proc, true, proc_name_match<&str_starts>);
|
||||
} else {
|
||||
kill_process(proc);
|
||||
}
|
||||
}
|
||||
|
||||
static int add_list(const char *pkg, const char *proc) {
|
||||
if (proc[0] == '\0')
|
||||
proc = pkg;
|
||||
|
||||
@ -105,15 +122,12 @@ static int add_list(const char *pkg, const char *proc = "") {
|
||||
char *err = db_exec(sql);
|
||||
db_err_cmd(err, return DAEMON_ERROR);
|
||||
|
||||
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
|
||||
|
||||
// Critical region
|
||||
{
|
||||
// Critical region
|
||||
mutex_guard lock(monitor_lock);
|
||||
hide_set.emplace(pkg, proc);
|
||||
add_hide_set(pkg, proc);
|
||||
}
|
||||
|
||||
kill_process(proc);
|
||||
return DAEMON_SUCCESS;
|
||||
}
|
||||
|
||||
@ -123,27 +137,28 @@ int add_list(int client) {
|
||||
int ret = add_list(pkg, proc);
|
||||
free(pkg);
|
||||
free(proc);
|
||||
update_uid_map();
|
||||
if (ret == DAEMON_SUCCESS)
|
||||
update_uid_map();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rm_list(const char *pkg, const char *proc = "") {
|
||||
static int rm_list(const char *pkg, const char *proc) {
|
||||
bool remove = false;
|
||||
{
|
||||
// Critical region
|
||||
mutex_guard lock(monitor_lock);
|
||||
bool remove = false;
|
||||
for (auto it = hide_set.begin(); it != hide_set.end();) {
|
||||
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
|
||||
remove = true;
|
||||
LOGI("hide_list rm: [%s]\n", it->second.data());
|
||||
LOGI("hide_list rm: [%s/%s]\n", it->first.data(), it->second.data());
|
||||
it = hide_set.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (!remove)
|
||||
return HIDE_ITEM_NOT_EXIST;
|
||||
}
|
||||
if (!remove)
|
||||
return HIDE_ITEM_NOT_EXIST;
|
||||
|
||||
char sql[4096];
|
||||
if (proc[0] == '\0')
|
||||
@ -167,10 +182,11 @@ int rm_list(int client) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void init_list(const char *pkg, const char *proc) {
|
||||
LOGI("hide_list init: [%s/%s]\n", pkg, proc);
|
||||
hide_set.emplace(pkg, proc);
|
||||
kill_process(proc);
|
||||
static bool str_ends_safe(string_view s, string_view ss) {
|
||||
// Never kill webview zygote
|
||||
if (s == "webview_zygote")
|
||||
return false;
|
||||
return str_ends(s, ss);
|
||||
}
|
||||
|
||||
#define SNET_PROC "com.google.android.gms.unstable"
|
||||
@ -181,25 +197,26 @@ static bool init_list() {
|
||||
LOGD("hide_list: initialize\n");
|
||||
|
||||
char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool {
|
||||
init_list(row["package_name"].data(), row["process"].data());
|
||||
add_hide_set(row["package_name"].data(), row["process"].data());
|
||||
return true;
|
||||
});
|
||||
db_err_cmd(err, return false);
|
||||
|
||||
// If Android Q+, also kill blastula pool
|
||||
// If Android Q+, also kill blastula pool and all app zygotes
|
||||
if (SDK_INT >= 29) {
|
||||
kill_process("usap32", true);
|
||||
kill_process("usap64", true);
|
||||
kill_process("_zygote", true, proc_name_match<&str_ends_safe>);
|
||||
}
|
||||
|
||||
// Add SafetyNet by default
|
||||
init_list(GMS_PKG, SNET_PROC);
|
||||
init_list(MICROG_PKG, SNET_PROC);
|
||||
add_hide_set(GMS_PKG, SNET_PROC);
|
||||
add_hide_set(MICROG_PKG, SNET_PROC);
|
||||
|
||||
// We also need to hide the default GMS process if MAGISKTMP != /sbin
|
||||
// The snet process communicates with the main process and get additional info
|
||||
if (MAGISKTMP != "/sbin")
|
||||
init_list(GMS_PKG, GMS_PKG);
|
||||
add_hide_set(GMS_PKG, GMS_PKG);
|
||||
|
||||
update_uid_map();
|
||||
return true;
|
||||
@ -251,7 +268,7 @@ int launch_magiskhide() {
|
||||
hide_late_sensitive_props();
|
||||
|
||||
// Start monitoring
|
||||
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); return nullptr; };
|
||||
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); };
|
||||
if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
|
||||
return DAEMON_ERROR;
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <daemon.hpp>
|
||||
|
||||
#define SIGTERMTHRD SIGUSR1
|
||||
#define ISOLATED_MAGIC "isolated"
|
||||
|
||||
// CLI entries
|
||||
int launch_magiskhide();
|
||||
|
@ -1,6 +1,3 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
@ -61,13 +58,13 @@ static int parse_ppid(int pid) {
|
||||
int ppid;
|
||||
|
||||
sprintf(path, "/proc/%d/stat", pid);
|
||||
FILE *stat = fopen(path, "re");
|
||||
if (stat == nullptr)
|
||||
|
||||
auto stat = open_file(path, "re");
|
||||
if (!stat)
|
||||
return -1;
|
||||
|
||||
// PID COMM STATE PPID .....
|
||||
fscanf(stat, "%*d %*s %*c %d", &ppid);
|
||||
fclose(stat);
|
||||
fscanf(stat.get(), "%*d %*s %*c %d", &ppid);
|
||||
|
||||
return ppid;
|
||||
}
|
||||
@ -89,20 +86,27 @@ void update_uid_map() {
|
||||
string data_path(APP_DATA_DIR);
|
||||
size_t len = data_path.length();
|
||||
auto dir = open_dir(APP_DATA_DIR);
|
||||
bool firstIteration = true;
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
data_path.resize(len);
|
||||
data_path += '/';
|
||||
data_path += entry->d_name;
|
||||
data_path += entry->d_name; // multiuser user id
|
||||
data_path += '/';
|
||||
size_t user_len = data_path.length();
|
||||
struct stat st;
|
||||
for (auto &hide : hide_set) {
|
||||
if (hide.first == ISOLATED_MAGIC) {
|
||||
if (!firstIteration) continue;
|
||||
// Setup isolated processes
|
||||
uid_proc_map[-1].emplace_back(hide.second);
|
||||
}
|
||||
data_path.resize(user_len);
|
||||
data_path += hide.first;
|
||||
if (stat(data_path.data(), &st))
|
||||
continue;
|
||||
uid_proc_map[st.st_uid].emplace_back(hide.second);
|
||||
}
|
||||
firstIteration = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +228,7 @@ static bool check_pid(int pid) {
|
||||
|
||||
sprintf(path, "/proc/%d", pid);
|
||||
if (stat(path, &st)) {
|
||||
// Process killed unexpectedly, ignore
|
||||
// Process died unexpectedly, ignore
|
||||
detach_pid(pid);
|
||||
return true;
|
||||
}
|
||||
@ -237,7 +241,7 @@ static bool check_pid(int pid) {
|
||||
if (auto f = open_file(path, "re")) {
|
||||
fgets(cmdline, sizeof(cmdline), f.get());
|
||||
} else {
|
||||
// Process killed unexpectedly, ignore
|
||||
// Process died unexpectedly, ignore
|
||||
detach_pid(pid);
|
||||
return true;
|
||||
}
|
||||
@ -247,36 +251,59 @@ static bool check_pid(int pid) {
|
||||
return false;
|
||||
|
||||
int uid = st.st_uid;
|
||||
auto it = uid_proc_map.find(uid);
|
||||
if (it != uid_proc_map.end()) {
|
||||
for (auto &s : it->second) {
|
||||
if (s == cmdline) {
|
||||
// Double check whether ns is separated
|
||||
read_ns(pid, &st);
|
||||
bool mnt_ns = true;
|
||||
for (auto &zit : zygote_map) {
|
||||
if (zit.second.st_ino == st.st_ino &&
|
||||
zit.second.st_dev == st.st_dev) {
|
||||
mnt_ns = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// For some reason ns is not separated, abort
|
||||
if (!mnt_ns)
|
||||
break;
|
||||
auto it = uid_proc_map.end();
|
||||
|
||||
// Finally this is our target!
|
||||
// Detach from ptrace but should still remain stopped.
|
||||
// The hide daemon will resume the process.
|
||||
PTRACE_LOG("target found\n");
|
||||
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||
detach_pid(pid, SIGSTOP);
|
||||
hide_daemon(pid);
|
||||
return true;
|
||||
if (uid % 100000 > 90000) {
|
||||
// Isolated process
|
||||
it = uid_proc_map.find(-1);
|
||||
if (it == uid_proc_map.end())
|
||||
goto not_target;
|
||||
for (auto &s : it->second) {
|
||||
if (str_starts(cmdline, s)) {
|
||||
LOGI("proc_monitor: (isolated) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||
goto inject_and_hide;
|
||||
}
|
||||
}
|
||||
}
|
||||
PTRACE_LOG("[%s] not our target\n", cmdline);
|
||||
|
||||
it = uid_proc_map.find(uid);
|
||||
if (it == uid_proc_map.end())
|
||||
goto not_target;
|
||||
for (auto &s : it->second) {
|
||||
if (s != cmdline)
|
||||
continue;
|
||||
|
||||
if (str_ends(s, "_zygote")) {
|
||||
LOGI("proc_monitor: (app zygote) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||
goto inject_and_hide;
|
||||
}
|
||||
|
||||
// Double check whether ns is separated
|
||||
read_ns(pid, &st);
|
||||
for (auto &zit : zygote_map) {
|
||||
if (zit.second.st_ino == st.st_ino &&
|
||||
zit.second.st_dev == st.st_dev) {
|
||||
// For some reason ns is not separated, abort
|
||||
goto not_target;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally this is our target!
|
||||
// Detach from ptrace but should still remain stopped.
|
||||
// The hide daemon will resume the process.
|
||||
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||
detach_pid(pid, SIGSTOP);
|
||||
hide_daemon(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
not_target:
|
||||
PTRACE_LOG("[%s] is not our target\n", cmdline);
|
||||
detach_pid(pid);
|
||||
return true;
|
||||
|
||||
inject_and_hide:
|
||||
// TODO: handle isolated processes and app zygotes
|
||||
detach_pid(pid);
|
||||
return true;
|
||||
}
|
||||
@ -324,7 +351,7 @@ static void new_zygote(int pid) {
|
||||
}
|
||||
|
||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||
#define DETACH_AND_CONT { detach = true; continue; }
|
||||
#define DETACH_AND_CONT { detach_pid(pid); continue; }
|
||||
|
||||
void proc_monitor() {
|
||||
// Unblock some signals
|
||||
@ -354,9 +381,7 @@ void proc_monitor() {
|
||||
setitimer(ITIMER_REAL, &interval, nullptr);
|
||||
}
|
||||
|
||||
int status;
|
||||
|
||||
for (;;) {
|
||||
for (int status;;) {
|
||||
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
|
||||
if (pid < 0) {
|
||||
if (errno == ECHILD) {
|
||||
@ -370,38 +395,35 @@ void proc_monitor() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
bool detach = false;
|
||||
run_finally f([&] {
|
||||
if (detach)
|
||||
// Non of our business now
|
||||
detach_pid(pid);
|
||||
});
|
||||
|
||||
if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
|
||||
DETACH_AND_CONT;
|
||||
|
||||
if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) {
|
||||
int event = WEVENT(status);
|
||||
int signal = WSTOPSIG(status);
|
||||
|
||||
if (signal == SIGTRAP && event) {
|
||||
unsigned long msg;
|
||||
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
|
||||
if (zygote_map.count(pid)) {
|
||||
// Zygote event
|
||||
switch (WEVENT(status)) {
|
||||
switch (event) {
|
||||
case PTRACE_EVENT_FORK:
|
||||
case PTRACE_EVENT_VFORK:
|
||||
PTRACE_LOG("zygote forked: [%d]\n", msg);
|
||||
PTRACE_LOG("zygote forked: [%lu]\n", msg);
|
||||
attaches[msg] = true;
|
||||
break;
|
||||
case PTRACE_EVENT_EXIT:
|
||||
PTRACE_LOG("zygote exited with status: [%d]\n", msg);
|
||||
PTRACE_LOG("zygote exited with status: [%lu]\n", msg);
|
||||
[[fallthrough]];
|
||||
default:
|
||||
zygote_map.erase(pid);
|
||||
DETACH_AND_CONT;
|
||||
}
|
||||
} else {
|
||||
switch (WEVENT(status)) {
|
||||
switch (event) {
|
||||
case PTRACE_EVENT_CLONE:
|
||||
PTRACE_LOG("create new threads: [%d]\n", msg);
|
||||
PTRACE_LOG("create new threads: [%lu]\n", msg);
|
||||
if (attaches[pid] && check_pid(pid))
|
||||
continue;
|
||||
break;
|
||||
@ -414,7 +436,7 @@ void proc_monitor() {
|
||||
}
|
||||
}
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
} else if (WSTOPSIG(status) == SIGSTOP) {
|
||||
} else if (signal == SIGSTOP) {
|
||||
if (!attaches[pid]) {
|
||||
// Double check if this is actually a process
|
||||
attaches[pid] = is_process(pid);
|
||||
@ -432,8 +454,8 @@ void proc_monitor() {
|
||||
}
|
||||
} else {
|
||||
// Not caused by us, resend signal
|
||||
xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status));
|
||||
PTRACE_LOG("signal [%d]\n", WSTOPSIG(status));
|
||||
xptrace(PTRACE_CONT, pid, nullptr, signal);
|
||||
PTRACE_LOG("signal [%d]\n", signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,12 +58,6 @@ int gen_rand_str(char *buf, int len, bool varlen) {
|
||||
return len - 1;
|
||||
}
|
||||
|
||||
int strend(const char *s1, const char *s2) {
|
||||
size_t l1 = strlen(s1);
|
||||
size_t l2 = strlen(s2);
|
||||
return strcmp(s1 + l1 - l2, s2);
|
||||
}
|
||||
|
||||
int exec_command(exec_t &exec) {
|
||||
int pipefd[] = {-1, -1};
|
||||
int outfd = -1;
|
||||
@ -151,12 +145,6 @@ void set_nice_name(const char *name) {
|
||||
prctl(PR_SET_NAME, name);
|
||||
}
|
||||
|
||||
bool ends_with(const std::string_view &s1, const std::string_view &s2) {
|
||||
unsigned l1 = s1.length();
|
||||
unsigned l2 = s2.length();
|
||||
return l1 < l2 ? false : s1.compare(l1 - l2, l2, s2) == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bionic's atoi runs through strtol().
|
||||
* Use our own implementation for faster conversion.
|
||||
|
@ -8,9 +8,6 @@
|
||||
#define UID_ROOT 0
|
||||
#define UID_SHELL 2000
|
||||
|
||||
#define str_contains(s, ss) ((ss) != nullptr && (s).find(ss) != std::string::npos)
|
||||
#define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0)
|
||||
|
||||
class mutex_guard {
|
||||
public:
|
||||
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
||||
@ -65,10 +62,18 @@ using thread_entry = void *(*)(void *);
|
||||
int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
|
||||
int new_daemon_thread(std::function<void()> &&entry);
|
||||
|
||||
bool ends_with(const std::string_view &s1, const std::string_view &s2);
|
||||
static inline bool str_contains(std::string_view s, std::string_view ss) {
|
||||
return s.find(ss) != std::string::npos;
|
||||
}
|
||||
static inline bool str_starts(std::string_view s, std::string_view ss) {
|
||||
return s.rfind(ss, 0) == 0;
|
||||
}
|
||||
static inline bool str_ends(std::string_view s, std::string_view ss) {
|
||||
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0;
|
||||
}
|
||||
|
||||
int fork_dont_care();
|
||||
int fork_no_orphan();
|
||||
int strend(const char *s1, const char *s2);
|
||||
void init_argv0(int argc, char **argv);
|
||||
void set_nice_name(const char *name);
|
||||
uint32_t binary_gcd(uint32_t u, uint32_t v);
|
||||
|
Loading…
Reference in New Issue
Block a user