mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 21:37:26 +00:00
Compare commits
34 Commits
manager-v8
...
manager-v8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fe8997efae | ||
![]() |
23455c722c | ||
![]() |
5ce29c30d2 | ||
![]() |
70d67728fd | ||
![]() |
e546884b08 | ||
![]() |
b36e6d987d | ||
![]() |
53c3dd5e8b | ||
![]() |
da723b207a | ||
![]() |
e050f77198 | ||
![]() |
540b4b7ea9 | ||
![]() |
bbef22daf7 | ||
![]() |
9ed110c91b | ||
![]() |
a30d510eb1 | ||
![]() |
ef98eaed8f | ||
![]() |
2a257f327c | ||
![]() |
4060c2107c | ||
![]() |
cd23d27048 | ||
![]() |
18b86e4fd2 | ||
![]() |
5f2e22a259 | ||
![]() |
4e97b18977 | ||
![]() |
f9bde347bc | ||
![]() |
947a7d6a2f | ||
![]() |
872ab2e99b | ||
![]() |
90b8813bb7 | ||
![]() |
88d0f63294 | ||
![]() |
79fa0d3a90 | ||
![]() |
8e61080a4a | ||
![]() |
3f9a64417b | ||
![]() |
eb959379e8 | ||
![]() |
41a644afb9 | ||
![]() |
6b42db943d | ||
![]() |
1c325459eb | ||
![]() |
6d88d8ad95 | ||
![]() |
246997f273 |
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## READ BEFORE OPENING ISSUES
|
||||
|
||||
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
|
||||
|
||||
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
|
||||
|
||||
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk Manager, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
|
||||
|
||||
If you experience a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`.
|
||||
|
||||
If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)
|
||||
|
||||
**DO NOT** open issues regarding root detection.
|
||||
|
||||
**DO NOT** ask for instructions.
|
||||
|
||||
**DO NOT** report issues if you have any modules installed.
|
||||
|
||||
Without following the rules above, your issue will be closed without explanation.
|
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -3,6 +3,13 @@ name: Magisk Build
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- 'native/**'
|
||||
- 'stub/**'
|
||||
- 'buildSrc/**'
|
||||
- 'build.py'
|
||||
- 'gradle.properties'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
@@ -43,6 +50,7 @@ jobs:
|
||||
echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV
|
||||
echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV
|
||||
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
|
||||
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Set up GitHub env (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
@@ -57,7 +65,6 @@ jobs:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/**/*.lock
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -25,6 +25,9 @@
|
||||
[submodule "pcre"]
|
||||
path = native/jni/external/pcre
|
||||
url = https://android.googlesource.com/platform/external/pcre
|
||||
[submodule "xhook"]
|
||||
path = native/jni/external/xhook
|
||||
url = https://github.com/iqiyi/xHook.git
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
|
@@ -15,11 +15,11 @@ Here are some feature highlights:
|
||||
|
||||
## Downloads
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.3/MagiskManager-v8.0.3.apk)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.4/MagiskManager-v8.0.4.apk)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
|
||||
<br>
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v20.4)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
@@ -25,6 +25,7 @@ object Const {
|
||||
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
|
||||
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
|
||||
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
|
||||
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
|
||||
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
|
||||
}
|
||||
|
||||
@@ -36,7 +37,6 @@ object Const {
|
||||
// notifications
|
||||
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
|
||||
const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||
const val HIDE_MANAGER_NOTIFICATION_ID = 8
|
||||
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
|
||||
|
@@ -27,13 +27,13 @@ class LocalModule(path: String) : Module() {
|
||||
val dir = "$PERSIST/$id"
|
||||
if (enable) {
|
||||
disableFile.delete()
|
||||
if (Const.Version.isCanary())
|
||||
if (Const.Version.atLeast_21_2())
|
||||
Shell.su("copy_sepolicy_rules").submit()
|
||||
else
|
||||
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
|
||||
} else {
|
||||
!disableFile.createNewFile()
|
||||
if (Const.Version.isCanary())
|
||||
if (Const.Version.atLeast_21_2())
|
||||
Shell.su("copy_sepolicy_rules").submit()
|
||||
else
|
||||
Shell.su("rm -rf $dir").submit()
|
||||
@@ -45,13 +45,13 @@ class LocalModule(path: String) : Module() {
|
||||
set(remove) {
|
||||
if (remove) {
|
||||
removeFile.createNewFile()
|
||||
if (Const.Version.isCanary())
|
||||
if (Const.Version.atLeast_21_2())
|
||||
Shell.su("copy_sepolicy_rules").submit()
|
||||
else
|
||||
Shell.su("rm -rf $PERSIST/$id").submit()
|
||||
} else {
|
||||
!removeFile.delete()
|
||||
if (Const.Version.isCanary())
|
||||
if (Const.Version.atLeast_21_2())
|
||||
Shell.su("copy_sepolicy_rules").submit()
|
||||
else
|
||||
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()
|
||||
|
@@ -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 }
|
||||
}
|
105
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
105
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
@@ -0,0 +1,105 @@
|
||||
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
|
||||
) {
|
||||
val isIsolated get() = name == ISOLATED_MAGIC
|
||||
val isAppZygote get() = name.endsWith("_zygote")
|
||||
}
|
@@ -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
|
||||
@@ -41,11 +40,10 @@ class HideItem(
|
||||
if (value == true) {
|
||||
processes
|
||||
.filterNot { it.isHidden }
|
||||
.filter { isExpanded || it.process.name == it.process.packageName }
|
||||
.filter { isExpanded || it.defaultSelection }
|
||||
} else {
|
||||
processes
|
||||
.filter { it.isHidden }
|
||||
.filter { isExpanded || it.process.name == it.process.packageName }
|
||||
}.forEach { it.toggle() }
|
||||
}
|
||||
|
||||
@@ -69,14 +67,19 @@ class HideItem(
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
processes.find { it.isHidden && it.process.name == it.process.packageName } != null
|
||||
val defaultProcesses = processes.filter { it.defaultSelection }
|
||||
when (defaultProcesses.count { it.isHidden }) {
|
||||
0 -> false
|
||||
defaultProcesses.size -> true
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +87,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 +106,10 @@ class HideProcessItem(
|
||||
isHidden = !isHidden
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = process == other.process
|
||||
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
|
||||
val defaultSelection get() =
|
||||
process.isIsolated || process.isAppZygote || process.name == process.packageName
|
||||
|
||||
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"
|
||||
|
@@ -1,3 +1,7 @@
|
||||
## v8.0.5
|
||||
|
||||
- Fix sepolicy rule copying
|
||||
|
||||
## v8.0.4
|
||||
|
||||
- A lot of stability changes and minor bug fixes
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<string name="no_connection">Keine Verbindung verfügbar</string>
|
||||
<string name="app_changelog">Änderungen</string>
|
||||
<string name="manager">Manager</string>
|
||||
<string name="loading">Lädt…</string>
|
||||
<string name="loading">Laden…</string>
|
||||
<string name="update">Update</string>
|
||||
<string name="not_available">N/A</string>
|
||||
<string name="hide">Verstecken</string>
|
||||
@@ -40,7 +40,7 @@
|
||||
<string name="recovery_mode">Recovery Modus</string>
|
||||
<string name="install_options_title">Optionen</string>
|
||||
<string name="install_method_title">Methode</string>
|
||||
<string name="install_next">Nächste</string>
|
||||
<string name="install_next">Nächster Schritt</string>
|
||||
<string name="install_start">Los geht\'s</string>
|
||||
<string name="manager_download_install">Tippen zum Herunterladen und Installieren</string>
|
||||
<string name="download_zip_only">Nur Zip herunterladen</string>
|
||||
|
@@ -44,7 +44,7 @@
|
||||
<string name="install_next">Ďalej</string>
|
||||
<string name="install_start">Poďme na to</string>
|
||||
<string name="manager_download_install">Stlačte pre stiahnutie a inštaláciu</string>
|
||||
<string name="download_zip_only">Len tiahnuť zip</string>
|
||||
<string name="download_zip_only">Len stiahnuť zip</string>
|
||||
<string name="direct_install">Priama inštalácia (Odporúča sa)</string>
|
||||
<string name="install_inactive_slot">Inštalovať na neaktívny slot (Po OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\nTúto voľbu použite iba po skončení OTA.\nPokračovať?</string>
|
||||
|
@@ -28,11 +28,11 @@
|
||||
<string name="home_support_content">Magisk është, dhe gjithmonë do të jetë, falas dhe me burim të hapur. Megjithatë mund të na tregoni se ju interesoni duke dërguar një donacion të vogël.</string>
|
||||
<string name="home_status_normal">Normale</string>
|
||||
<string name="home_status_stub">Stub</string>
|
||||
<string name="home_installed_version">Instaluar</string>
|
||||
<string name="home_latest_version">Më të fundit</string>
|
||||
<string name="home_installed_version">E instaluar</string>
|
||||
<string name="home_latest_version">Më e fundit</string>
|
||||
<string name="invalid_update_channel">Kanal i pavlefshëm i azhurnimit</string>
|
||||
<string name="uninstall_magisk_title">Çinstalo Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Të gjitha modulet do të çaktivizohen/hiqen! \n Rrënja do të hiqet! \n Të dhënat tuaja potencialisht të koduara nëse jo tashmë!</string>
|
||||
<string name="uninstall_magisk_msg">Të gjitha modulet do të çaktivizohen/hiqen! \n Rrënja do të hiqet! \n\n Të dhënat tuaja potencialisht të koduara nëse jo tashmë!</string>
|
||||
<string name="home_check_safetynet">Kontrolloni Rrjetin e Sigurisë</string>
|
||||
|
||||
<!--Install-->
|
||||
@@ -95,7 +95,7 @@
|
||||
<string name="safetynet_res_invalid">Përgjigjja është e pavlefshme </string>
|
||||
<string name="safetynet_attest_success">Suksese!</string>
|
||||
<string name="safetynet_attest_failure">Vërtetimi dështoi!</string>
|
||||
<string name="safetynet_attest_loading">Thjesht një sekondë…</string>
|
||||
<string name="safetynet_attest_loading">Prit një sekondë…</string>
|
||||
<string name="safetynet_attest_restart">Provo përsëri</string>
|
||||
|
||||
<!--MagiskHide-->
|
||||
@@ -111,7 +111,7 @@
|
||||
<string name="reboot_userspace">Ristartim normal</string>
|
||||
<string name="reboot_recovery">Ristartoni në recovery</string>
|
||||
<string name="reboot_bootloader">Ristartoni në bootloader</string>
|
||||
<string name="reboot_download">Ristartoni për në download</string>
|
||||
<string name="reboot_download">Ristartoni në download</string>
|
||||
<string name="reboot_edl">Ristartoni në EDL</string>
|
||||
<string name="module_version_author">%1$s nga %2$s</string>
|
||||
<string name="module_state_remove">Hiq</string>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# Release Notes
|
||||
|
||||
- [v21.2](21200.md)
|
||||
- [v21.1](21100.md)
|
||||
- [v21.0](21000.md)
|
||||
- [v20.4](20400.md)
|
||||
|
@@ -27,6 +27,6 @@ android.injected.testOnly=false
|
||||
kapt.incremental.apt=true
|
||||
|
||||
# Magisk
|
||||
magisk.versionCode=21200
|
||||
magisk.versionCode=21201
|
||||
magisk.ndkVersion=21d
|
||||
magisk.fullNdkVersion=21.3.6528147
|
||||
|
@@ -8,7 +8,7 @@ ifdef B_MAGISK
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magisk
|
||||
LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils
|
||||
LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils libxhook
|
||||
LOCAL_C_INCLUDES := jni/include
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
@@ -30,7 +30,10 @@ LOCAL_SRC_FILES := \
|
||||
su/su.cpp \
|
||||
su/connect.cpp \
|
||||
su/pts.cpp \
|
||||
su/su_daemon.cpp
|
||||
su/su_daemon.cpp \
|
||||
inject/entry.cpp \
|
||||
inject/utils.cpp \
|
||||
inject/hook.cpp
|
||||
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_EXECUTABLE)
|
||||
@@ -140,15 +143,16 @@ include $(BUILD_EXECUTABLE)
|
||||
endif
|
||||
|
||||
ifdef B_TEST
|
||||
ifneq (,$(wildcard jni/test.cpp))
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := test
|
||||
LOCAL_STATIC_LIBRARIES := libutils
|
||||
LOCAL_C_INCLUDES := jni/include
|
||||
LOCAL_SRC_FILES := test.cpp
|
||||
LOCAL_LDFLAGS := -static
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef B_BB
|
||||
@@ -161,5 +165,4 @@ endif
|
||||
# Libraries
|
||||
########################
|
||||
include jni/utils/Android.mk
|
||||
include jni/systemproperties/Android.mk
|
||||
include jni/external/Android.mk
|
||||
|
@@ -8,21 +8,26 @@
|
||||
#include <selinux.hpp>
|
||||
#include <utils.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
using namespace std;
|
||||
|
||||
using main_fun = int (*)(int, char *[]);
|
||||
|
||||
static main_fun applet_main[] = { su_client_main, resetprop_main, magiskhide_main, nullptr };
|
||||
|
||||
[[noreturn]] static void call_applet(int argc, char **argv) {
|
||||
static int call_applet(int argc, char *argv[]) {
|
||||
// Applets
|
||||
string_view base = basename(argv[0]);
|
||||
for (int i = 0; applet_names[i]; ++i) {
|
||||
if (strcmp(basename(argv[0]), applet_names[i]) == 0) {
|
||||
exit((*applet_main[i])(argc, argv));
|
||||
if (base == applet_names[i]) {
|
||||
return (*applet_main[i])(argc, argv);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s: applet not found\n", basename(argv[0]));
|
||||
exit(1);
|
||||
if (str_starts(base, "app_process")) {
|
||||
return app_process_main(argc, argv);
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: applet not found\n", base.data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -41,6 +46,6 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
call_applet(argc, argv);
|
||||
return call_applet(argc, argv);
|
||||
}
|
||||
|
||||
|
@@ -189,21 +189,22 @@ static int magisk_log(int prio, const char *fmt, va_list ap) {
|
||||
return vfprintf(local_log_file.get(), buf, args);
|
||||
}
|
||||
|
||||
static void android_logging() {
|
||||
#define mlog(prio) [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_##prio, fmt, ap); }
|
||||
static void magisk_logging() {
|
||||
auto in_mem_file = make_stream_fp<byte_stream>(log_buf, log_buf_len);
|
||||
log_file.reset(in_mem_file.release(), [](FILE *) {
|
||||
free(log_buf);
|
||||
log_buf = nullptr;
|
||||
});
|
||||
log_cb.d = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_DEBUG, fmt, ap); };
|
||||
log_cb.i = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_INFO, fmt, ap); };
|
||||
log_cb.w = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_WARN, fmt, ap); };
|
||||
log_cb.e = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_ERROR, fmt, ap); };
|
||||
log_cb.d = mlog(DEBUG);
|
||||
log_cb.i = mlog(INFO);
|
||||
log_cb.w = mlog(WARN);
|
||||
log_cb.e = mlog(ERROR);
|
||||
log_cb.ex = nop_ex;
|
||||
}
|
||||
|
||||
static void daemon_entry(int ppid) {
|
||||
android_logging();
|
||||
magisk_logging();
|
||||
|
||||
int fd = xopen("/dev/null", O_WRONLY);
|
||||
xdup2(fd, STDOUT_FILENO);
|
||||
|
21
native/jni/external/Android.mk
vendored
21
native/jni/external/Android.mk
vendored
@@ -353,4 +353,23 @@ LOCAL_SRC_FILES := \
|
||||
pcre/dist2/src/pcre2_xclass.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(LOCAL_PATH)/mincrypt/Android.mk
|
||||
# libxhook.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libxhook
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/xhook/libxhook/jni
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
|
||||
LOCAL_CONLYFLAGS := -std=c11
|
||||
LOCAL_SRC_FILES := \
|
||||
xhook/libxhook/jni/xh_log.c \
|
||||
xhook/libxhook/jni/xh_version.c \
|
||||
xhook/libxhook/jni/xh_jni.c \
|
||||
xhook/libxhook/jni/xhook.c \
|
||||
xhook/libxhook/jni/xh_core.c \
|
||||
xhook/libxhook/jni/xh_util.c \
|
||||
xhook/libxhook/jni/xh_elf.c
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
CWD := $(LOCAL_PATH)
|
||||
include $(CWD)/systemproperties/Android.mk
|
||||
include $(CWD)/mincrypt/Android.mk
|
||||
|
1
native/jni/external/xhook
vendored
Submodule
1
native/jni/external/xhook
vendored
Submodule
Submodule native/jni/external/xhook added at 9180bd7409
@@ -36,3 +36,4 @@ int magiskhide_main(int argc, char *argv[]);
|
||||
int magiskpolicy_main(int argc, char *argv[]);
|
||||
int su_client_main(int argc, char *argv[]);
|
||||
int resetprop_main(int argc, char *argv[]);
|
||||
int app_process_main(int argc, char *argv[]);
|
||||
|
@@ -160,6 +160,8 @@ void load_kernel_info(cmdline *cmd) {
|
||||
cmd->skip_initramfs = true;
|
||||
} else if (key == "androidboot.force_normal_boot") {
|
||||
cmd->force_normal_boot = value[0] == '1';
|
||||
} else if (key == "rootwait") {
|
||||
cmd->rootwait = true;
|
||||
} else if (key == "androidboot.android_dt_dir") {
|
||||
strcpy(cmd->dt_dir, value);
|
||||
} else if (key == "androidboot.hardware") {
|
||||
@@ -174,6 +176,7 @@ void load_kernel_info(cmdline *cmd) {
|
||||
LOGD("Kernel cmdline info:\n");
|
||||
LOGD("skip_initramfs=[%d]\n", cmd->skip_initramfs);
|
||||
LOGD("force_normal_boot=[%d]\n", cmd->force_normal_boot);
|
||||
LOGD("rootwait=[%d]\n", cmd->rootwait);
|
||||
LOGD("slot=[%s]\n", cmd->slot);
|
||||
LOGD("dt_dir=[%s]\n", cmd->dt_dir);
|
||||
LOGD("fstab_suffix=[%s]\n", cmd->fstab_suffix);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
struct cmdline {
|
||||
bool skip_initramfs;
|
||||
bool force_normal_boot;
|
||||
bool rootwait;
|
||||
char slot[3];
|
||||
char dt_dir[64];
|
||||
char fstab_suffix[32];
|
||||
|
@@ -309,6 +309,8 @@ void SARBase::backup_files() {
|
||||
void SARBase::mount_system_root() {
|
||||
LOGD("Early mount system_root\n");
|
||||
strcpy(blk_info.block_dev, "/dev/root");
|
||||
|
||||
do {
|
||||
// Try legacy SAR dm-verity
|
||||
strcpy(blk_info.partname, "vroot");
|
||||
auto dev = setup_block(false);
|
||||
@@ -326,6 +328,9 @@ void SARBase::mount_system_root() {
|
||||
if (dev >= 0)
|
||||
goto mount_root;
|
||||
|
||||
// Poll forever if rootwait was given in cmdline
|
||||
} while (cmd->rootwait);
|
||||
|
||||
// We don't really know what to do at this point...
|
||||
LOGE("Cannot find root partition, abort\n");
|
||||
exit(1);
|
||||
|
@@ -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));
|
||||
|
151
native/jni/inject/entry.cpp
Normal file
151
native/jni/inject/entry.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include <libgen.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <android/log.h>
|
||||
#include <atomic>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void *self_handle = nullptr;
|
||||
static atomic<int> active_threads = -1;
|
||||
|
||||
#define alog(prio) [](auto fmt, auto ap){ \
|
||||
return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); }
|
||||
static void inject_logging() {
|
||||
log_cb.d = alog(DEBUG);
|
||||
log_cb.i = alog(INFO);
|
||||
log_cb.w = alog(WARN);
|
||||
log_cb.e = alog(ERROR);
|
||||
log_cb.ex = nop_ex;
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void inject_cleanup() {
|
||||
if (active_threads < 0)
|
||||
return;
|
||||
|
||||
// Setup 1ms
|
||||
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
|
||||
|
||||
// Check flag in busy loop
|
||||
while (active_threads)
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
// Wait another 1ms to make sure all threads left our code
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
|
||||
void self_unload() {
|
||||
LOGD("hook: Request to self unload\n");
|
||||
// If unhook failed, do not unload or else it will cause SIGSEGV
|
||||
if (!unhook_functions())
|
||||
return;
|
||||
new_daemon_thread(reinterpret_cast<void *(*)(void *)>(&dlclose), self_handle);
|
||||
active_threads--;
|
||||
}
|
||||
|
||||
static void *unload_first_stage(void *) {
|
||||
// Setup 1ms
|
||||
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
|
||||
|
||||
while (getenv(INJECT_ENV_1))
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
// Wait another 1ms to make sure all threads left our code
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
unmap_all(INJECT_LIB_1);
|
||||
active_threads--;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make sure /proc/self/environ does not reveal our secrets
|
||||
// Copy all env to a contiguous memory and set the memory region as MM_ENV
|
||||
static void sanitize_environ() {
|
||||
static string env;
|
||||
|
||||
for (int i = 0; environ[i]; ++i) {
|
||||
if (str_starts(environ[i], INJECT_ENV_1 "="))
|
||||
continue;
|
||||
env += environ[i];
|
||||
env += '\0';
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
bool success = true;
|
||||
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_START, env.data(), 0, 0));
|
||||
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_END, env.data() + env.size(), 0, 0));
|
||||
if (success)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
static void inject_init() {
|
||||
inject_logging();
|
||||
|
||||
if (getenv(INJECT_ENV_2)) {
|
||||
LOGD("zygote: inject 2nd stage\n");
|
||||
active_threads = 1;
|
||||
unsetenv(INJECT_ENV_2);
|
||||
|
||||
// Get our own handle
|
||||
self_handle = dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
dlclose(self_handle);
|
||||
|
||||
hook_functions();
|
||||
|
||||
// Some cleanup
|
||||
sanitize_environ();
|
||||
active_threads++;
|
||||
new_daemon_thread(&unload_first_stage);
|
||||
} else if (char *env = getenv(INJECT_ENV_1)) {
|
||||
LOGD("zygote: inject 1st stage\n");
|
||||
|
||||
if (env[0] == '1')
|
||||
unsetenv("LD_PRELOAD");
|
||||
else
|
||||
setenv("LD_PRELOAD", env, 1); // Restore original LD_PRELOAD
|
||||
|
||||
// Setup second stage
|
||||
setenv(INJECT_ENV_2, "1", 1);
|
||||
cp_afc(INJECT_LIB_1, INJECT_LIB_2);
|
||||
dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
|
||||
unsetenv(INJECT_ENV_1);
|
||||
}
|
||||
}
|
||||
|
||||
int app_process_main(int argc, char *argv[]) {
|
||||
inject_logging();
|
||||
char buf[4096];
|
||||
if (realpath("/proc/self/exe", buf) == nullptr)
|
||||
return 1;
|
||||
|
||||
int in = xopen(buf, O_RDONLY);
|
||||
int out = xopen(INJECT_LIB_1, O_CREAT | O_WRONLY | O_TRUNC, 0777);
|
||||
sendfile(out, in, nullptr, INT_MAX);
|
||||
close(in);
|
||||
close(out);
|
||||
|
||||
if (char *ld = getenv("LD_PRELOAD")) {
|
||||
char env[128];
|
||||
sprintf(env, "%s:" INJECT_LIB_1, ld);
|
||||
setenv("LD_PRELOAD", env, 1);
|
||||
setenv(INJECT_ENV_1, ld, 1); // Backup original LD_PRELOAD
|
||||
} else {
|
||||
setenv("LD_PRELOAD", INJECT_LIB_1, 1);
|
||||
setenv(INJECT_ENV_1, "1", 1);
|
||||
}
|
||||
|
||||
// Execute real app_process
|
||||
xumount2(buf, MNT_DETACH);
|
||||
execve(buf, argv, environ);
|
||||
return 1;
|
||||
}
|
247
native/jni/inject/hook.cpp
Normal file
247
native/jni/inject/hook.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include <jni.h>
|
||||
|
||||
#include <xhook.h>
|
||||
#include <utils.hpp>
|
||||
#include <flags.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DCL_HOOK_FUNC(ret, func, ...) \
|
||||
static ret (*old_##func)(__VA_ARGS__); \
|
||||
static ret new_##func(__VA_ARGS__)
|
||||
|
||||
#define DCL_JNI_FUNC(name) \
|
||||
static const JNINativeMethod *name##_orig = nullptr; \
|
||||
extern const JNINativeMethod name##_methods[]; \
|
||||
extern const int name##_methods_num;
|
||||
|
||||
// For some reason static vectors won't work, use pointers instead
|
||||
static vector<tuple<const char *, const char *, void **>> *xhook_list;
|
||||
static vector<JNINativeMethod> *jni_list;
|
||||
|
||||
static JavaVM *g_jvm;
|
||||
static int prev_fork_pid = -1;
|
||||
|
||||
namespace {
|
||||
|
||||
struct HookContext {
|
||||
int pid;
|
||||
bool unload;
|
||||
};
|
||||
|
||||
// JNI method declarations
|
||||
DCL_JNI_FUNC(nativeForkAndSpecialize)
|
||||
DCL_JNI_FUNC(nativeSpecializeAppProcess)
|
||||
DCL_JNI_FUNC(nativeForkSystemServer)
|
||||
|
||||
}
|
||||
|
||||
#define HOOK_JNI(method) \
|
||||
if (newMethods[i].name == #method##sv) { \
|
||||
auto orig = new JNINativeMethod(); \
|
||||
memcpy(orig, &newMethods[i], sizeof(JNINativeMethod)); \
|
||||
method##_orig = orig; \
|
||||
jni_list->push_back(newMethods[i]); \
|
||||
for (int j = 0; j < method##_methods_num; ++j) { \
|
||||
if (strcmp(newMethods[i].signature, method##_methods[j].signature) == 0) { \
|
||||
newMethods[i] = method##_methods[j]; \
|
||||
LOGI("hook: replaced #" #method "\n"); \
|
||||
++hooked; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
continue; \
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(int, jniRegisterNativeMethods,
|
||||
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) {
|
||||
LOGD("hook: jniRegisterNativeMethods %s", className);
|
||||
|
||||
unique_ptr<JNINativeMethod[]> newMethods;
|
||||
int hooked = 0;
|
||||
|
||||
if (g_jvm == nullptr) {
|
||||
// Save for later unhooking
|
||||
env->GetJavaVM(&g_jvm);
|
||||
}
|
||||
|
||||
if (className == "com/android/internal/os/Zygote"sv) {
|
||||
newMethods = make_unique<JNINativeMethod[]>(numMethods);
|
||||
memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);
|
||||
for (int i = 0; i < numMethods && hooked < 3; ++i) {
|
||||
HOOK_JNI(nativeForkAndSpecialize);
|
||||
HOOK_JNI(nativeSpecializeAppProcess);
|
||||
HOOK_JNI(nativeForkSystemServer);
|
||||
}
|
||||
}
|
||||
|
||||
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(int, fork) {
|
||||
if (prev_fork_pid < 0)
|
||||
return old_fork();
|
||||
|
||||
// Skip an actual fork and return the previous fork result
|
||||
int pid = prev_fork_pid;
|
||||
prev_fork_pid = -1;
|
||||
return pid;
|
||||
}
|
||||
|
||||
static int sigmask(int how, int signum) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, signum);
|
||||
return sigprocmask(how, &set, nullptr);
|
||||
}
|
||||
|
||||
static int pre_specialize_fork() {
|
||||
// First block SIGCHLD, unblock after original fork is done
|
||||
sigmask(SIG_BLOCK, SIGCHLD);
|
||||
prev_fork_pid = old_fork();
|
||||
return prev_fork_pid;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static void nativeForkAndSpecialize_pre(HookContext *ctx,
|
||||
JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jintArray &fds_to_close, jintArray &fds_to_ignore, /* These 2 arguments are unique to fork */
|
||||
jboolean &is_child_zygote, jstring &instruction_set, jstring &app_data_dir,
|
||||
jboolean &is_top_app, jobjectArray &pkg_data_info_list,
|
||||
jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs,
|
||||
jboolean &mount_storage_dirs) {
|
||||
|
||||
// Do our own fork before loading any 3rd party code
|
||||
ctx->pid = pre_specialize_fork();
|
||||
if (ctx->pid != 0)
|
||||
return;
|
||||
|
||||
// TODO: check if we need to do hiding
|
||||
// Demonstrate self unload in child process
|
||||
ctx->unload = true;
|
||||
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
static void nativeForkAndSpecialize_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
// Unblock SIGCHLD in case the original method didn't
|
||||
sigmask(SIG_UNBLOCK, SIGCHLD);
|
||||
|
||||
if (ctx->pid != 0)
|
||||
return;
|
||||
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
|
||||
if (ctx->unload)
|
||||
self_unload();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static void nativeSpecializeAppProcess_pre(HookContext *ctx,
|
||||
JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jboolean &is_child_zygote, jstring &instruction_set, jstring &app_data_dir,
|
||||
jboolean &is_top_app, jobjectArray &pkg_data_info_list,
|
||||
jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs,
|
||||
jboolean &mount_storage_dirs) {
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
static void nativeSpecializeAppProcess_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static void nativeForkSystemServer_pre(HookContext *ctx,
|
||||
JNIEnv *env, jclass clazz, uid_t &uid, gid_t &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jlong &permitted_capabilities, jlong &effective_capabilities) {
|
||||
|
||||
// Do our own fork before loading any 3rd party code
|
||||
ctx->pid = pre_specialize_fork();
|
||||
if (ctx->pid != 0)
|
||||
return;
|
||||
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
static void nativeForkSystemServer_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
// Unblock SIGCHLD in case the original method didn't
|
||||
sigmask(SIG_UNBLOCK, SIGCHLD);
|
||||
|
||||
if (ctx->pid != 0)
|
||||
return;
|
||||
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static bool hook_refresh() {
|
||||
if (xhook_refresh(0) == 0) {
|
||||
xhook_clear();
|
||||
LOGI("hook: xhook success\n");
|
||||
return true;
|
||||
} else {
|
||||
LOGE("hook: xhook failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int hook_register(const char *path, const char *symbol, void *new_func, void **old_func) {
|
||||
int ret = xhook_register(path, symbol, new_func, old_func);
|
||||
if (ret != 0) {
|
||||
LOGE("hook: Failed to register hook \"%s\"\n", symbol);
|
||||
return ret;
|
||||
}
|
||||
xhook_list->emplace_back(path, symbol, old_func);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define XHOOK_REGISTER(PATH_REGEX, NAME) \
|
||||
hook_register(PATH_REGEX, #NAME, (void*) new_##NAME, (void **) &old_##NAME)
|
||||
|
||||
void hook_functions() {
|
||||
#ifdef MAGISK_DEBUG
|
||||
xhook_enable_debug(1);
|
||||
xhook_enable_sigsegv_protection(0);
|
||||
#endif
|
||||
xhook_list = new remove_pointer_t<decltype(xhook_list)>();
|
||||
jni_list = new remove_pointer_t<decltype(jni_list)>();
|
||||
|
||||
XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods);
|
||||
XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork);
|
||||
hook_refresh();
|
||||
}
|
||||
|
||||
bool unhook_functions() {
|
||||
JNIEnv* env;
|
||||
if (g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||
return false;
|
||||
|
||||
// Unhook JNI methods
|
||||
if (!jni_list->empty() && old_jniRegisterNativeMethods(env,
|
||||
"com/android/internal/os/Zygote",
|
||||
jni_list->data(), jni_list->size()) != 0) {
|
||||
LOGE("hook: Failed to register JNI hook\n");
|
||||
return false;
|
||||
}
|
||||
delete jni_list;
|
||||
|
||||
// Unhook xhook
|
||||
for (auto &[path, sym, old_func] : *xhook_list) {
|
||||
if (xhook_register(path, sym, *old_func, nullptr) != 0) {
|
||||
LOGE("hook: Failed to register hook \"%s\"\n", sym);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
delete xhook_list;
|
||||
return hook_refresh();
|
||||
}
|
||||
|
||||
#include "jni_hooks.hpp"
|
22
native/jni/inject/inject.hpp
Normal file
22
native/jni/inject/inject.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define INJECT_LIB_1 "/dev/tmp/magisk.1.so"
|
||||
#define INJECT_LIB_2 "/dev/tmp/magisk.2.so"
|
||||
#define INJECT_ENV_1 "MAGISK_INJ_1"
|
||||
#define INJECT_ENV_2 "MAGISK_INJ_2"
|
||||
|
||||
// Unmap all pages matching the name
|
||||
void unmap_all(const char *name);
|
||||
|
||||
// Get library name and base address that contains the function
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib);
|
||||
|
||||
// Get library base address with name
|
||||
uintptr_t get_remote_lib(int pid, const char *lib);
|
||||
|
||||
void self_unload();
|
||||
void hook_functions();
|
||||
bool unhook_functions();
|
366
native/jni/inject/jni_hooks.hpp
Normal file
366
native/jni/inject/jni_hooks.hpp
Normal file
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Original code: https://github.com/RikkaApps/Riru/blob/master/riru/src/main/cpp/jni_native_method.cpp
|
||||
* The code is modified and sublicensed to GPLv3 for incorporating into Magisk.
|
||||
*
|
||||
* Copyright (c) 2018-2021, RikkaW
|
||||
* Copyright (c) 2021, John 'topjohnwu' Wu
|
||||
*/
|
||||
|
||||
#define ENABLE_LEGACY_DP 0 // Nobody should use outdated developer preview...
|
||||
|
||||
// All possible missing arguments
|
||||
static union {
|
||||
struct {
|
||||
jintArray fds_to_ignore;
|
||||
jboolean is_child_zygote;
|
||||
jboolean is_top_app;
|
||||
jobjectArray pkg_data_info_list;
|
||||
jobjectArray whitelisted_data_info_list;
|
||||
jboolean mount_data_dirs;
|
||||
jboolean mount_storage_dirs;
|
||||
};
|
||||
size_t args_buf[8]; // Easy access to wipe all variables at once
|
||||
};
|
||||
|
||||
#define DCL_JNI(ret, name, sig, ...) \
|
||||
const static char name##_sig[] = sig; \
|
||||
static ret name(__VA_ARGS__)
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#define pre_fork() \
|
||||
HookContext ctx{}; \
|
||||
memset(args_buf, 0, sizeof(args_buf)); \
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz, uid, gid, gids, runtime_flags, \
|
||||
rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, \
|
||||
instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, \
|
||||
mount_data_dirs, mount_storage_dirs)
|
||||
|
||||
#define orig_fork(ver, ...) \
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_##ver)> \
|
||||
(nativeForkAndSpecialize_orig->fnPtr)(__VA_ARGS__)
|
||||
|
||||
#define post_fork() \
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz); \
|
||||
return ctx.pid
|
||||
|
||||
#define DCL_FORK_AND_SPECIALIZE(ver, sig, ...) \
|
||||
DCL_JNI(jint, nativeForkAndSpecialize_##ver, sig, __VA_ARGS__)
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(m,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(m, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, fds_to_close, instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(o,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(o, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(p,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(p, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(q_alt,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
pre_fork();
|
||||
orig_fork(q_alt, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
#if ENABLE_LEGACY_DP
|
||||
DCL_FORK_AND_SPECIALIZE(r_dp2,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list) {
|
||||
pre_fork();
|
||||
orig_fork(r_dp2, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir,
|
||||
is_top_app, pkg_data_info_list);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(r_dp3,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list,
|
||||
jboolean mount_storage_dirs) {
|
||||
pre_fork();
|
||||
orig_fork(r_dp3, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set,
|
||||
app_data_dir, is_top_app, pkg_data_info_list, mount_storage_dirs);
|
||||
post_fork();
|
||||
}
|
||||
#endif // ENABLE_LEGACY_DP
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(r,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list,
|
||||
jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
pre_fork();
|
||||
orig_fork(r, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app,
|
||||
pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(samsung_m,
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
|
||||
jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(samsung_m, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, category, accessInfo, nice_name, fds_to_close, instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(samsung_n,
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
|
||||
jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint a1) {
|
||||
pre_fork();
|
||||
orig_fork(samsung_n, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, category, accessInfo, nice_name, fds_to_close, instruction_set, app_data_dir, a1);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(samsung_o,
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
|
||||
jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set,
|
||||
jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(samsung_o, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, category, accessInfo, nice_name, fds_to_close, fds_to_ignore,
|
||||
instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
DCL_FORK_AND_SPECIALIZE(samsung_p,
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo,
|
||||
jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
|
||||
jstring instruction_set, jstring app_data_dir) {
|
||||
pre_fork();
|
||||
orig_fork(samsung_p, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, category, accessInfo, nice_name, fds_to_close, fds_to_ignore, is_child_zygote,
|
||||
instruction_set, app_data_dir);
|
||||
post_fork();
|
||||
}
|
||||
|
||||
#define DEF_FORK(ver) { \
|
||||
"nativeForkAndSpecialize", \
|
||||
nativeForkAndSpecialize_##ver##_sig, \
|
||||
(void *) &nativeForkAndSpecialize_##ver \
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#define pre_spec() \
|
||||
HookContext ctx{}; \
|
||||
memset(args_buf, 0, sizeof(args_buf)); \
|
||||
nativeSpecializeAppProcess_pre(&ctx, \
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, \
|
||||
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, \
|
||||
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs)
|
||||
|
||||
#define orig_spec(ver, ...) \
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_##ver)> \
|
||||
(nativeSpecializeAppProcess_orig->fnPtr)(__VA_ARGS__)
|
||||
|
||||
#define post_spec() \
|
||||
nativeSpecializeAppProcess_post(&ctx, env, clazz)
|
||||
|
||||
#define DCL_SPECIALIZE_APP(ver, sig, ...) \
|
||||
DCL_JNI(void, nativeSpecializeAppProcess_##ver, sig, __VA_ARGS__)
|
||||
|
||||
DCL_SPECIALIZE_APP(q,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
pre_spec();
|
||||
orig_spec(q, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir);
|
||||
post_spec();
|
||||
}
|
||||
|
||||
DCL_SPECIALIZE_APP(q_alt,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
|
||||
jboolean is_top_app) {
|
||||
pre_spec();
|
||||
orig_spec(q_alt, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app);
|
||||
post_spec();
|
||||
}
|
||||
|
||||
#if ENABLE_LEGACY_DP
|
||||
DCL_SPECIALIZE_APP(r_dp2,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
|
||||
jboolean is_top_app, jobjectArray pkg_data_info_list) {
|
||||
pre_spec();
|
||||
orig_spec(r_dp2, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list);
|
||||
post_spec();
|
||||
}
|
||||
|
||||
DCL_SPECIALIZE_APP(r_dp3,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
|
||||
jboolean is_top_app, jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
|
||||
pre_spec();
|
||||
orig_spec(r_dp3, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
|
||||
mount_storage_dirs);
|
||||
post_spec();
|
||||
}
|
||||
#endif // ENABLE_LEGACY_DP
|
||||
|
||||
DCL_SPECIALIZE_APP(r,
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
|
||||
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
|
||||
jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
|
||||
jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
pre_spec();
|
||||
orig_spec(r, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name,
|
||||
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
|
||||
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs);
|
||||
post_spec();
|
||||
}
|
||||
|
||||
DCL_SPECIALIZE_APP(samsung_q,
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jint mount_external, jstring se_info, jint space, jint accessInfo,
|
||||
jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
pre_spec();
|
||||
orig_spec(samsung_q, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, space, accessInfo, nice_name, is_child_zygote, instruction_set, app_data_dir);
|
||||
post_spec();
|
||||
}
|
||||
|
||||
#define DEF_SPEC(ver) { \
|
||||
"nativeSpecializeAppProcess", \
|
||||
nativeSpecializeAppProcess_##ver##_sig, \
|
||||
(void *) &nativeSpecializeAppProcess_##ver \
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#define pre_server() \
|
||||
HookContext ctx{}; \
|
||||
memset(args_buf, 0, sizeof(args_buf)); \
|
||||
nativeForkSystemServer_pre(&ctx, env, clazz, uid, gid, gids, runtime_flags, \
|
||||
rlimits, permitted_capabilities, effective_capabilities)
|
||||
|
||||
#define orig_server(ver, ...) \
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_##ver)> \
|
||||
(nativeForkSystemServer_orig->fnPtr)(__VA_ARGS__)
|
||||
|
||||
#define post_server() \
|
||||
nativeForkSystemServer_post(&ctx, env, clazz); \
|
||||
return ctx.pid
|
||||
|
||||
#define DCL_FORK_SERVER(ver, sig, ...) \
|
||||
DCL_JNI(jint, nativeForkSystemServer_##ver, sig, __VA_ARGS__)
|
||||
|
||||
DCL_FORK_SERVER(m, "(II[II[[IJJ)I",
|
||||
JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
|
||||
jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
pre_server();
|
||||
orig_server(m, env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
|
||||
effective_capabilities);
|
||||
post_server();
|
||||
}
|
||||
|
||||
DCL_FORK_SERVER(samsung_q, "(II[IIII[[IJJ)I",
|
||||
JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
|
||||
jint space, jint accessInfo, jobjectArray rlimits, jlong permitted_capabilities,
|
||||
jlong effective_capabilities) {
|
||||
pre_server();
|
||||
orig_server(samsung_q, env, clazz, uid, gid, gids, runtime_flags, space, accessInfo, rlimits,
|
||||
permitted_capabilities, effective_capabilities);
|
||||
post_server();
|
||||
}
|
||||
|
||||
#define DEF_SERVER(ver) { \
|
||||
"nativeForkSystemServer", \
|
||||
nativeForkSystemServer_##ver##_sig, \
|
||||
(void *) &nativeForkSystemServer_##ver \
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const JNINativeMethod nativeForkAndSpecialize_methods[] = {
|
||||
DEF_FORK(m), DEF_FORK(o), DEF_FORK(p),
|
||||
DEF_FORK(q_alt), DEF_FORK(r),
|
||||
DEF_FORK(samsung_m), DEF_FORK(samsung_n),
|
||||
DEF_FORK(samsung_o), DEF_FORK(samsung_p),
|
||||
#if ENABLE_LEGACY_DP
|
||||
DEF_FORK(r_dp2), DEF_FORK(r_dp3)
|
||||
#endif
|
||||
};
|
||||
const int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods);
|
||||
|
||||
const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
|
||||
DEF_SPEC(q), DEF_SPEC(q_alt),
|
||||
DEF_SPEC(r), DEF_SPEC(samsung_q),
|
||||
#if ENABLE_LEGACY_DP
|
||||
DEF_SPEC(r_dp2), DEF_SPEC(r_dp3)
|
||||
#endif
|
||||
};
|
||||
const int nativeSpecializeAppProcess_methods_num = std::size(
|
||||
nativeSpecializeAppProcess_methods);
|
||||
|
||||
const JNINativeMethod nativeForkSystemServer_methods[] = {
|
||||
DEF_SERVER(m), DEF_SERVER(samsung_q)
|
||||
};
|
||||
const int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);
|
||||
|
||||
}
|
241
native/jni/inject/ptrace.cpp
Normal file
241
native/jni/inject/ptrace.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Original code: https://github.com/Chainfire/injectvm-binderjack/blob/master/app/src/main/jni/libinject/inject.cpp
|
||||
* The code is heavily modified and sublicensed to GPLv3 for incorporating into Magisk.
|
||||
*
|
||||
* Copyright (c) 2015, Simone 'evilsocket' Margaritelli
|
||||
* Copyright (c) 2015-2019, Jorrit 'Chainfire' Jongma
|
||||
* Copyright (c) 2021, John 'topjohnwu' Wu
|
||||
*
|
||||
* See original LICENSE file from the original project for additional details:
|
||||
* https://github.com/Chainfire/injectvm-binderjack/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTE:
|
||||
* The code in this file was originally planned to be used for some features,
|
||||
* but it turned out to be unsuitable for the task. However, this shall remain
|
||||
* in our arsenal in case it may be used in the future.
|
||||
*/
|
||||
|
||||
#include <elf.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
#include "ptrace.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#if defined(__arm__)
|
||||
#define CPSR_T_MASK (1u << 5)
|
||||
#define PARAMS_IN_REGS 4
|
||||
#elif defined(__aarch64__)
|
||||
#define CPSR_T_MASK (1u << 5)
|
||||
#define PARAMS_IN_REGS 8
|
||||
#define pt_regs user_pt_regs
|
||||
#define uregs regs
|
||||
#define ARM_pc pc
|
||||
#define ARM_sp sp
|
||||
#define ARM_cpsr pstate
|
||||
#define ARM_lr regs[30]
|
||||
#define ARM_r0 regs[0]
|
||||
#endif
|
||||
|
||||
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast<void*>(addr + i));
|
||||
if (data < 0)
|
||||
return false;
|
||||
memcpy(static_cast<uint8_t *>(buf) + i, &data, std::min(len - i, sizeof(data)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long data = 0;
|
||||
memcpy(&data, static_cast<const uint8_t *>(buf) + i, std::min(len - i, sizeof(data)));
|
||||
if (xptrace(PTRACE_POKETEXT, pid, reinterpret_cast<void*>(addr + i), data) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get remote registers
|
||||
#define remote_getregs(regs) _remote_getregs(pid, regs)
|
||||
static void _remote_getregs(int pid, pt_regs *regs) {
|
||||
#if defined(__LP64__)
|
||||
uintptr_t regset = NT_PRSTATUS;
|
||||
iovec iov{};
|
||||
iov.iov_base = regs;
|
||||
iov.iov_len = sizeof(*regs);
|
||||
xptrace(PTRACE_GETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
|
||||
#else
|
||||
xptrace(PTRACE_GETREGS, pid, nullptr, regs);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Set remote registers
|
||||
#define remote_setregs(regs) _remote_setregs(pid, regs)
|
||||
static void _remote_setregs(int pid, pt_regs *regs) {
|
||||
#if defined(__LP64__)
|
||||
uintptr_t regset = NT_PRSTATUS;
|
||||
iovec iov{};
|
||||
iov.iov_base = regs;
|
||||
iov.iov_len = sizeof(*regs);
|
||||
xptrace(PTRACE_SETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
|
||||
#else
|
||||
xptrace(PTRACE_SETREGS, pid, nullptr, regs);
|
||||
#endif
|
||||
}
|
||||
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) {
|
||||
pt_regs regs, regs_bak;
|
||||
|
||||
// Get registers and save a backup
|
||||
remote_getregs(®s);
|
||||
memcpy(®s_bak, ®s, sizeof(regs));
|
||||
|
||||
// ABI dependent: Setup stack and registers to perform the call
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
// Fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters
|
||||
for (int i = 0; (i < nargs) && (i < PARAMS_IN_REGS); ++i) {
|
||||
regs.uregs[i] = va_arg(va, uintptr_t);
|
||||
}
|
||||
|
||||
// Push remaining parameters onto stack
|
||||
if (nargs > PARAMS_IN_REGS) {
|
||||
regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS);
|
||||
uintptr_t stack = regs.ARM_sp;
|
||||
for (int i = PARAMS_IN_REGS; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Set return address
|
||||
regs.ARM_lr = 0;
|
||||
|
||||
// Set function address to call
|
||||
regs.ARM_pc = func_addr;
|
||||
|
||||
// Setup the current processor status register
|
||||
if (regs.ARM_pc & 1u) {
|
||||
// thumb
|
||||
regs.ARM_pc &= (~1u);
|
||||
regs.ARM_cpsr |= CPSR_T_MASK;
|
||||
} else {
|
||||
// arm
|
||||
regs.ARM_cpsr &= ~CPSR_T_MASK;
|
||||
}
|
||||
#elif defined(__i386__)
|
||||
// Push all params onto stack
|
||||
regs.esp -= sizeof(uintptr_t) * nargs;
|
||||
uintptr_t stack = regs.esp;
|
||||
for (int i = 0; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
// Push return address onto stack
|
||||
uintptr_t ret_addr = 0;
|
||||
regs.esp -= sizeof(uintptr_t);
|
||||
remote_write(regs.esp, &ret_addr, sizeof(uintptr_t));
|
||||
|
||||
// Set function address to call
|
||||
regs.eip = func_addr;
|
||||
#elif defined(__x86_64__)
|
||||
// Align, rsp - 8 must be a multiple of 16 at function entry point
|
||||
uintptr_t space = sizeof(uintptr_t);
|
||||
if (nargs > 6)
|
||||
space += sizeof(uintptr_t) * (nargs - 6);
|
||||
while (((regs.rsp - space - 8) & 0xF) != 0)
|
||||
regs.rsp--;
|
||||
|
||||
// Fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters
|
||||
for (int i = 0; (i < nargs) && (i < 6); ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
switch (i) {
|
||||
case 0: regs.rdi = arg; break;
|
||||
case 1: regs.rsi = arg; break;
|
||||
case 2: regs.rdx = arg; break;
|
||||
case 3: regs.rcx = arg; break;
|
||||
case 4: regs.r8 = arg; break;
|
||||
case 5: regs.r9 = arg; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Push remaining parameters onto stack
|
||||
if (nargs > 6) {
|
||||
regs.rsp -= sizeof(uintptr_t) * (nargs - 6);
|
||||
uintptr_t stack = regs.rsp;
|
||||
for(int i = 6; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Push return address onto stack
|
||||
uintptr_t ret_addr = 0;
|
||||
regs.rsp -= sizeof(uintptr_t);
|
||||
remote_write(regs.rsp, &ret_addr, sizeof(uintptr_t));
|
||||
|
||||
// Set function address to call
|
||||
regs.rip = func_addr;
|
||||
|
||||
// may be needed
|
||||
regs.rax = 0;
|
||||
regs.orig_rax = 0;
|
||||
#else
|
||||
#error Unsupported ABI
|
||||
#endif
|
||||
|
||||
// Resume process to do the call
|
||||
remote_setregs(®s);
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
|
||||
// Catch SIGSEGV caused by the 0 return address
|
||||
int status;
|
||||
while (waitpid(pid, &status, __WALL | __WNOTHREAD) == pid) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV))
|
||||
break;
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
}
|
||||
|
||||
// Get registers again for return value
|
||||
remote_getregs(®s);
|
||||
|
||||
// Restore registers
|
||||
remote_setregs(®s_bak);
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
return regs.ARM_r0;
|
||||
#elif defined(__i386__)
|
||||
return regs.eax;
|
||||
#elif defined(__x86_64__)
|
||||
return regs.rax;
|
||||
#endif
|
||||
}
|
||||
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) {
|
||||
char lib_name[4096];
|
||||
auto local = get_function_lib(addr, lib_name);
|
||||
if (local == 0)
|
||||
return 0;
|
||||
auto remote = get_remote_lib(pid, lib_name);
|
||||
if (remote == 0)
|
||||
return 0;
|
||||
addr = addr - local + remote;
|
||||
va_list va;
|
||||
va_start(va, nargs);
|
||||
auto result = remote_call_abi(pid, addr, nargs, va);
|
||||
va_end(va);
|
||||
return result;
|
||||
}
|
27
native/jni/inject/ptrace.hpp
Normal file
27
native/jni/inject/ptrace.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Write bytes to the remote process at addr
|
||||
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len);
|
||||
#define remote_write(...) _remote_write(pid, __VA_ARGS__)
|
||||
|
||||
// Read bytes from the remote process at addr
|
||||
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len);
|
||||
#define remote_read(...) _remote_read(pid, __VA_ARGS__)
|
||||
|
||||
// Call a remote function
|
||||
// Arguments are expected to be only integer-like or pointer types
|
||||
// as other more complex C ABIs are not implemented.
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va);
|
||||
|
||||
// Find remote offset and invoke function
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...);
|
||||
|
||||
// C++ wrapper for auto argument counting and casting function pointers
|
||||
template<class FuncPtr, class ...Args>
|
||||
static uintptr_t _remote_call(int pid, FuncPtr sym, Args && ...args) {
|
||||
auto addr = reinterpret_cast<uintptr_t>(sym);
|
||||
return remote_call_vararg(pid, addr, sizeof...(args), std::forward<Args>(args)...);
|
||||
}
|
||||
#define remote_call(...) _remote_call(pid, __VA_ARGS__)
|
101
native/jni/inject/utils.cpp
Normal file
101
native/jni/inject/utils.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
struct map_info {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
int perms;
|
||||
char *path;
|
||||
|
||||
map_info() : start(0), end(0), perms(0), path(nullptr) {}
|
||||
|
||||
enum {
|
||||
EXEC = (1 << 0),
|
||||
WRITE = (1 << 1),
|
||||
READ = (1 << 2),
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename Func>
|
||||
static void parse_maps(int pid, Func fn) {
|
||||
char file[32];
|
||||
|
||||
// format: start-end perms offset dev inode path
|
||||
sprintf(file, "/proc/%d/maps", pid);
|
||||
file_readline(true, file, [=](string_view l) -> bool {
|
||||
char *pos = (char *) l.data();
|
||||
map_info info;
|
||||
|
||||
// Parse address hex strings
|
||||
info.start = strtoul(pos, &pos, 16);
|
||||
info.end = strtoul(++pos, &pos, 16);
|
||||
|
||||
// Parse permissions
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::READ;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::WRITE;
|
||||
if (*(++pos) != '-')
|
||||
info.perms |= map_info::EXEC;
|
||||
pos += 3;
|
||||
|
||||
// Skip everything except path
|
||||
int path_off;
|
||||
sscanf(pos, "%*s %*s %*s %n%*s", &path_off);
|
||||
pos += path_off;
|
||||
info.path = pos;
|
||||
|
||||
return fn(info);
|
||||
});
|
||||
}
|
||||
|
||||
void unmap_all(const char *name) {
|
||||
vector<map_info> unmaps;
|
||||
parse_maps(getpid(), [=, &unmaps](map_info &info) -> bool {
|
||||
if (strcmp(info.path, name) == 0)
|
||||
unmaps.emplace_back(info);
|
||||
return true;
|
||||
});
|
||||
for (map_info &info : unmaps) {
|
||||
void *addr = reinterpret_cast<void *>(info.start);
|
||||
size_t size = info.end - info.start;
|
||||
munmap(addr, size);
|
||||
if (info.perms & map_info::READ) {
|
||||
// Make sure readable pages are still readable
|
||||
xmmap(addr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t get_function_lib(uintptr_t addr, char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(getpid(), [=, &base](map_info &info) -> bool {
|
||||
if (addr >= info.start && addr < info.end) {
|
||||
if (lib)
|
||||
strcpy(lib, info.path);
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
uintptr_t get_remote_lib(int pid, const char *lib) {
|
||||
uintptr_t base = 0;
|
||||
parse_maps(pid, [=, &base](map_info &info) -> bool {
|
||||
if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) {
|
||||
base = info.start;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return base;
|
||||
}
|
@@ -30,7 +30,7 @@ static const char *late_prop_val[] =
|
||||
{ "green", nullptr };
|
||||
|
||||
void hide_sensitive_props() {
|
||||
LOGI("hide_policy: Hiding sensitive props\n");
|
||||
LOGI("hide: Hiding sensitive props\n");
|
||||
|
||||
for (int i = 0; prop_key[i]; ++i) {
|
||||
auto value = getprop(prop_key[i]);
|
||||
@@ -63,7 +63,7 @@ void hide_sensitive_props() {
|
||||
}
|
||||
|
||||
void hide_late_sensitive_props() {
|
||||
LOGI("hide_policy: Hiding sensitive props (late)\n");
|
||||
LOGI("hide: Hiding sensitive props (late)\n");
|
||||
|
||||
for (int i = 0; late_prop_key[i]; ++i) {
|
||||
auto value = getprop(late_prop_key[i]);
|
||||
@@ -74,9 +74,10 @@ void hide_late_sensitive_props() {
|
||||
|
||||
static void lazy_unmount(const char* mountpoint) {
|
||||
if (umount2(mountpoint, MNT_DETACH) != -1)
|
||||
LOGD("hide_policy: Unmounted (%s)\n", mountpoint);
|
||||
LOGD("hide: Unmounted (%s)\n", mountpoint);
|
||||
}
|
||||
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
void hide_daemon(int pid) {
|
||||
if (fork_dont_care() == 0) {
|
||||
hide_unmount(pid);
|
||||
@@ -85,15 +86,16 @@ void hide_daemon(int pid) {
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && \
|
||||
strncmp(mentry->mnt_dir, "/" #dir, sizeof("/" #dir) - 1) == 0)
|
||||
|
||||
void hide_unmount(int pid) {
|
||||
if (switch_mnt_ns(pid))
|
||||
if (pid > 0 && switch_mnt_ns(pid))
|
||||
return;
|
||||
|
||||
LOGD("hide_policy: handling PID=[%d]\n", pid);
|
||||
LOGD("hide: handling PID=[%d]\n", pid);
|
||||
|
||||
char val;
|
||||
int fd = xopen(SELINUX_ENFORCE, O_RDONLY);
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <set>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <utils.hpp>
|
||||
@@ -15,11 +13,46 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
static pthread_t proc_monitor_thread;
|
||||
static bool hide_state = false;
|
||||
static set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
|
||||
map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
|
||||
|
||||
// This locks the 2 variables above
|
||||
static pthread_mutex_t hide_state_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
// Locks the variables above
|
||||
pthread_mutex_t hide_state_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
static pthread_t monitor_thread;
|
||||
#endif
|
||||
|
||||
void update_uid_map() {
|
||||
mutex_guard lock(hide_state_lock);
|
||||
uid_proc_map.clear();
|
||||
string data_path(APP_DATA_DIR);
|
||||
size_t len = data_path.length();
|
||||
auto dir = open_dir(APP_DATA_DIR);
|
||||
bool first_iter = true;
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
data_path.resize(len);
|
||||
data_path += '/';
|
||||
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 (!first_iter) 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);
|
||||
}
|
||||
first_iter = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Leave /proc fd opened as we're going to read from it repeatedly
|
||||
static DIR *procfp;
|
||||
@@ -43,28 +76,27 @@ bool hide_enabled() {
|
||||
return hide_state;
|
||||
}
|
||||
|
||||
void set_hide_state(bool state) {
|
||||
mutex_guard g(hide_state_lock);
|
||||
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: 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 +104,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 +121,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 +150,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
|
||||
{
|
||||
mutex_guard lock(monitor_lock);
|
||||
hide_set.emplace(pkg, proc);
|
||||
// Critical region
|
||||
mutex_guard lock(hide_state_lock);
|
||||
add_hide_set(pkg, proc);
|
||||
}
|
||||
|
||||
kill_process(proc);
|
||||
return DAEMON_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -123,27 +165,28 @@ int add_list(int client) {
|
||||
int ret = add_list(pkg, proc);
|
||||
free(pkg);
|
||||
free(proc);
|
||||
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;
|
||||
mutex_guard lock(hide_state_lock);
|
||||
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;
|
||||
}
|
||||
|
||||
char sql[4096];
|
||||
if (proc[0] == '\0')
|
||||
@@ -167,10 +210,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"
|
||||
@@ -178,28 +222,29 @@ static void init_list(const char *pkg, const char *proc) {
|
||||
#define MICROG_PKG "org.microg.gms.droidguard"
|
||||
|
||||
static bool init_list() {
|
||||
LOGD("hide_list: initialize\n");
|
||||
LOGD("hide: 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;
|
||||
@@ -237,10 +282,7 @@ int launch_magiskhide() {
|
||||
if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr)
|
||||
return DAEMON_ERROR;
|
||||
|
||||
LOGI("* Starting MagiskHide\n");
|
||||
|
||||
// Initialize the mutex lock
|
||||
pthread_mutex_init(&monitor_lock, nullptr);
|
||||
LOGI("* Enable MagiskHide\n");
|
||||
|
||||
// Initialize the hide list
|
||||
if (!init_list())
|
||||
@@ -250,10 +292,11 @@ int launch_magiskhide() {
|
||||
if (DAEMON_STATE >= STATE_BOOT_COMPLETE || DAEMON_STATE == STATE_NONE)
|
||||
hide_late_sensitive_props();
|
||||
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
// Start monitoring
|
||||
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); return nullptr; };
|
||||
if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
|
||||
if (new_daemon_thread(&proc_monitor))
|
||||
return DAEMON_ERROR;
|
||||
#endif
|
||||
|
||||
hide_state = true;
|
||||
update_hide_config();
|
||||
@@ -264,8 +307,12 @@ int stop_magiskhide() {
|
||||
mutex_guard g(hide_state_lock);
|
||||
|
||||
if (hide_state) {
|
||||
LOGI("* Stopping MagiskHide\n");
|
||||
pthread_kill(proc_monitor_thread, SIGTERMTHRD);
|
||||
LOGI("* Disable MagiskHide\n");
|
||||
uid_proc_map.clear();
|
||||
hide_set.clear();
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
pthread_kill(monitor_thread, SIGTERMTHRD);
|
||||
#endif
|
||||
}
|
||||
|
||||
hide_state = false;
|
||||
@@ -275,7 +322,9 @@ int stop_magiskhide() {
|
||||
|
||||
void auto_start_magiskhide() {
|
||||
if (hide_enabled()) {
|
||||
pthread_kill(proc_monitor_thread, SIGALRM);
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
pthread_kill(monitor_thread, SIGALRM);
|
||||
#endif
|
||||
hide_late_sensitive_props();
|
||||
} else if (SDK_INT >= 19) {
|
||||
db_settings dbs;
|
||||
@@ -285,8 +334,10 @@ void auto_start_magiskhide() {
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
void test_proc_monitor() {
|
||||
if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr)
|
||||
exit(1);
|
||||
proc_monitor();
|
||||
}
|
||||
#endif
|
||||
|
@@ -105,7 +105,7 @@ int magiskhide_main(int argc, char *argv[]) {
|
||||
execvp(argv[2], argv + 2);
|
||||
exit(1);
|
||||
}
|
||||
#if 0
|
||||
#if 0 && ENABLE_PTRACE_MONITOR
|
||||
else if (opt == "test"sv)
|
||||
test_proc_monitor();
|
||||
#endif
|
||||
|
@@ -5,14 +5,17 @@
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <daemon.hpp>
|
||||
|
||||
#define SIGTERMTHRD SIGUSR1
|
||||
#define ISOLATED_MAGIC "isolated"
|
||||
|
||||
// Global toggle for ptrace monitor
|
||||
#define ENABLE_PTRACE_MONITOR 1
|
||||
|
||||
// CLI entries
|
||||
int launch_magiskhide();
|
||||
@@ -20,26 +23,27 @@ int stop_magiskhide();
|
||||
int add_list(int client);
|
||||
int rm_list(int client);
|
||||
void ls_list(int client);
|
||||
[[noreturn]] void test_proc_monitor();
|
||||
|
||||
#if ENABLE_PTRACE_MONITOR
|
||||
// Process monitoring
|
||||
[[noreturn]] void proc_monitor();
|
||||
void update_uid_map();
|
||||
[[noreturn]] void test_proc_monitor();
|
||||
#endif
|
||||
|
||||
// Utility functions
|
||||
void crawl_procfs(const std::function<bool (int)> &fn);
|
||||
void crawl_procfs(DIR *dir, const std::function<bool (int)> &fn);
|
||||
bool hide_enabled();
|
||||
void set_hide_state(bool state);
|
||||
void update_uid_map();
|
||||
|
||||
// Hide policies
|
||||
void hide_daemon(int pid);
|
||||
void hide_unmount(int pid = getpid());
|
||||
void hide_unmount(int pid = -1);
|
||||
void hide_sensitive_props();
|
||||
void hide_late_sensitive_props();
|
||||
|
||||
extern pthread_mutex_t monitor_lock;
|
||||
extern std::set<std::pair<std::string, std::string>> hide_set;
|
||||
extern pthread_mutex_t hide_state_lock;
|
||||
extern std::map<int, std::vector<std::string_view>> uid_proc_map;
|
||||
|
||||
enum {
|
||||
LAUNCH_MAGISKHIDE,
|
||||
|
@@ -1,6 +1,3 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
@@ -21,18 +18,11 @@ using namespace std;
|
||||
|
||||
static int inotify_fd = -1;
|
||||
|
||||
static void term_thread(int sig = SIGTERMTHRD);
|
||||
static void new_zygote(int pid);
|
||||
|
||||
/**********************
|
||||
* All data structures
|
||||
**********************/
|
||||
|
||||
set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
|
||||
static map<int, struct stat> zygote_map; /* zygote pid -> mnt ns */
|
||||
static map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
|
||||
|
||||
pthread_mutex_t monitor_lock;
|
||||
/******************
|
||||
* Data structures
|
||||
******************/
|
||||
|
||||
#define PID_MAX 32768
|
||||
struct pid_set {
|
||||
@@ -44,7 +34,10 @@ private:
|
||||
};
|
||||
|
||||
// true if pid is monitored
|
||||
pid_set attaches;
|
||||
static pid_set attaches;
|
||||
|
||||
// zygote pid -> mnt ns
|
||||
static map<int, struct stat> zygote_map;
|
||||
|
||||
/********
|
||||
* Utils
|
||||
@@ -61,51 +54,17 @@ 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;
|
||||
}
|
||||
|
||||
static inline long xptrace(int request, pid_t pid, void *addr, void *data) {
|
||||
long ret = ptrace(request, pid, addr, data);
|
||||
if (ret < 0)
|
||||
PLOGE("ptrace %d", pid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_t data = 0) {
|
||||
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
|
||||
}
|
||||
|
||||
void update_uid_map() {
|
||||
mutex_guard lock(monitor_lock);
|
||||
uid_proc_map.clear();
|
||||
string data_path(APP_DATA_DIR);
|
||||
size_t len = data_path.length();
|
||||
auto dir = open_dir(APP_DATA_DIR);
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
data_path.resize(len);
|
||||
data_path += '/';
|
||||
data_path += entry->d_name;
|
||||
data_path += '/';
|
||||
size_t user_len = data_path.length();
|
||||
struct stat st;
|
||||
for (auto &hide : hide_set) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_zygote_done() {
|
||||
#ifdef __LP64__
|
||||
return zygote_map.size() >= 2;
|
||||
@@ -139,7 +98,7 @@ static void check_zygote() {
|
||||
static void setup_inotify() {
|
||||
inotify_fd = xinotify_init1(IN_CLOEXEC);
|
||||
if (inotify_fd < 0)
|
||||
term_thread();
|
||||
return;
|
||||
|
||||
// Setup inotify asynchronous I/O
|
||||
fcntl(inotify_fd, F_SETFL, O_ASYNC);
|
||||
@@ -167,8 +126,8 @@ static void setup_inotify() {
|
||||
************************/
|
||||
|
||||
static void inotify_event(int) {
|
||||
/* Make sure we can actually read stuffs
|
||||
* or else the whole thread will be blocked.*/
|
||||
// Make sure we can actually read stuffs
|
||||
// or else the whole thread will be blocked.
|
||||
struct pollfd pfd = {
|
||||
.fd = inotify_fd,
|
||||
.events = POLLIN,
|
||||
@@ -187,13 +146,8 @@ static void inotify_event(int) {
|
||||
// Workaround for the lack of pthread_cancel
|
||||
static void term_thread(int) {
|
||||
LOGD("proc_monitor: cleaning up\n");
|
||||
uid_proc_map.clear();
|
||||
zygote_map.clear();
|
||||
hide_set.clear();
|
||||
attaches.reset();
|
||||
// Misc
|
||||
set_hide_state(false);
|
||||
pthread_mutex_destroy(&monitor_lock);
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
LOGD("proc_monitor: terminate\n");
|
||||
@@ -224,7 +178,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 +191,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 +201,44 @@ 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()) {
|
||||
|
||||
// Start accessing uid_proc_map
|
||||
mutex_guard lock(hide_state_lock);
|
||||
auto it = uid_proc_map.end();
|
||||
|
||||
if (uid % 100000 > 90000) {
|
||||
// No way to handle isolated process
|
||||
goto not_target;
|
||||
}
|
||||
|
||||
it = uid_proc_map.find(uid);
|
||||
if (it == uid_proc_map.end())
|
||||
goto not_target;
|
||||
for (auto &s : it->second) {
|
||||
if (s == cmdline) {
|
||||
// Double check whether ns is separated
|
||||
if (s != cmdline)
|
||||
continue;
|
||||
|
||||
// Check if ns is separated (could be app zygote)
|
||||
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;
|
||||
// ns not separated, abort
|
||||
goto not_target;
|
||||
}
|
||||
}
|
||||
// For some reason ns is not separated, abort
|
||||
if (!mnt_ns)
|
||||
break;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
PTRACE_LOG("[%s] not our target\n", cmdline);
|
||||
|
||||
not_target:
|
||||
PTRACE_LOG("[%s] is not our target\n", cmdline);
|
||||
detach_pid(pid);
|
||||
return true;
|
||||
}
|
||||
@@ -323,8 +285,7 @@ static void new_zygote(int pid) {
|
||||
xptrace(PTRACE_CONT, 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 +315,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 +329,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 +370,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 +388,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;
|
||||
@@ -119,23 +113,30 @@ int exec_command_sync(exec_t &exec) {
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg, const pthread_attr_t *attr) {
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
int ret = xpthread_create(&thread, attr, entry, arg);
|
||||
if (ret == 0)
|
||||
pthread_detach(thread);
|
||||
return ret;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
return xpthread_create(&thread, &attr, entry, arg);
|
||||
}
|
||||
|
||||
static void *proxy_routine(void *fp) {
|
||||
int new_daemon_thread(void(*entry)()) {
|
||||
thread_entry proxy = [](void *entry) -> void * {
|
||||
reinterpret_cast<void(*)()>(entry)();
|
||||
return nullptr;
|
||||
};
|
||||
return new_daemon_thread(proxy, (void *) entry);
|
||||
}
|
||||
|
||||
int new_daemon_thread(std::function<void()> &&entry) {
|
||||
thread_entry proxy = [](void *fp) -> void * {
|
||||
auto fn = reinterpret_cast<std::function<void()>*>(fp);
|
||||
(*fn)();
|
||||
delete fn;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int new_daemon_thread(std::function<void()> &&entry) {
|
||||
return new_daemon_thread(proxy_routine, new std::function<void()>(std::move(entry)));
|
||||
};
|
||||
return new_daemon_thread(proxy, new std::function<void()>(std::move(entry)));
|
||||
}
|
||||
|
||||
static char *argv0;
|
||||
@@ -151,12 +152,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) {
|
||||
@@ -62,13 +59,22 @@ static inline int parse_int(const std::string &s) { return parse_int(s.data());
|
||||
static inline int parse_int(std::string_view s) { return parse_int(s.data()); }
|
||||
|
||||
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(thread_entry entry, void *arg = nullptr);
|
||||
int new_daemon_thread(void(*entry)());
|
||||
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);
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include <sys/mount.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
@@ -470,3 +471,10 @@ int xmknod(const char *pathname, mode_t mode, dev_t dev) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
long xptrace(int request, pid_t pid, void *addr, void *data) {
|
||||
long ret = ptrace(request, pid, addr, data);
|
||||
if (ret < 0)
|
||||
PLOGE("ptrace %d", pid);
|
||||
return ret;
|
||||
}
|
||||
|
@@ -60,4 +60,8 @@ int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
int xinotify_init1(int flags);
|
||||
char *xrealpath(const char *path, char *resolved_path);
|
||||
int xmknod(const char *pathname, mode_t mode, dev_t dev);
|
||||
|
||||
long xptrace(int request, pid_t pid, void *addr = nullptr, void *data = nullptr);
|
||||
static inline long xptrace(int request, pid_t pid, void *addr, uintptr_t data) {
|
||||
return xptrace(request, pid, addr, reinterpret_cast<void *>(data));
|
||||
}
|
||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||
|
@@ -3,12 +3,13 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
val canary = !Config["appVersion"].orEmpty().contains(".")
|
||||
val canary = !Config.appVersion.contains(".")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
versionCode = 1
|
||||
versionName = Config.appVersion
|
||||
buildConfigField("int", "STUB_VERSION", "15")
|
||||
buildConfigField("String", "DEV_CHANNEL", Config["DEV_CHANNEL"] ?: "null")
|
||||
buildConfigField("boolean", "CANARY", if (canary) "true" else "false")
|
||||
}
|
||||
@@ -16,11 +17,15 @@ android {
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
isShrinkResources = false
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
additionalParameters("--package-id", "0x80")
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
|
@@ -5,25 +5,82 @@
|
||||
package="com.topjohnwu.magisk">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
tools:ignore="AllowBackup">
|
||||
android:appComponentFactory=".DelegateComponentFactory"
|
||||
android:name="a.e"
|
||||
android:allowBackup="false"
|
||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning" >
|
||||
|
||||
<activity android:name=".MainActivity">
|
||||
<!-- Splash -->
|
||||
<activity
|
||||
android:name="a.c">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Main -->
|
||||
<activity android:name="a.b" />
|
||||
|
||||
<!-- Superuser -->
|
||||
<activity
|
||||
android:name="a.m"
|
||||
android:directBootAware="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
tools:ignore="AppLinkUrlError">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Receiver -->
|
||||
<receiver
|
||||
android:name="a.h"
|
||||
android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.REBOOT" />
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- DownloadService -->
|
||||
<service android:name="a.j" />
|
||||
|
||||
<!-- FileProvider -->
|
||||
<provider
|
||||
android:name=".FileProvider"
|
||||
android:name="a.p"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:directBootAware="true"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
</provider>
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="12451000" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
6
stub/src/main/java/a/c.java
Normal file
6
stub/src/main/java/a/c.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DownloadActivity;
|
||||
|
||||
public class c extends DownloadActivity {
|
||||
}
|
6
stub/src/main/java/a/e.java
Normal file
6
stub/src/main/java/a/e.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DelegateApplication;
|
||||
|
||||
public class e extends DelegateApplication {
|
||||
}
|
6
stub/src/main/java/a/h.java
Normal file
6
stub/src/main/java/a/h.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||
|
||||
public class h extends DummyReceiver {
|
||||
}
|
6
stub/src/main/java/a/p.java
Normal file
6
stub/src/main/java/a/p.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.FileProvider;
|
||||
|
||||
public class p extends FileProvider {
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class DelegateApplication extends Application {
|
||||
|
||||
private Application delegate;
|
||||
static boolean dynLoad = false;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
// Only dynamic load full APK if hidden and possible
|
||||
dynLoad = Build.VERSION.SDK_INT >= 28 &&
|
||||
!base.getPackageName().equals(BuildConfig.APPLICATION_ID);
|
||||
if (!dynLoad)
|
||||
return;
|
||||
|
||||
delegate = InjectAPK.setup(this);
|
||||
if (delegate != null) try {
|
||||
// Call attachBaseContext without ContextImpl to show it is being wrapped
|
||||
Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
|
||||
m.setAccessible(true);
|
||||
m.invoke(delegate, this);
|
||||
} catch (Exception ignored) { /* Impossible */ }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (delegate != null)
|
||||
delegate.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AppComponentFactory;
|
||||
import android.app.Application;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.dummy.DummyProvider;
|
||||
import com.topjohnwu.magisk.dummy.DummyReceiver;
|
||||
import com.topjohnwu.magisk.dummy.DummyService;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class DelegateComponentFactory extends AppComponentFactory {
|
||||
|
||||
ClassLoader loader;
|
||||
AppComponentFactory delegate;
|
||||
|
||||
interface DummyFactory<T> {
|
||||
T create();
|
||||
}
|
||||
|
||||
public DelegateComponentFactory() {
|
||||
InjectAPK.factory = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Application instantiateApplication(ClassLoader cl, String className) {
|
||||
if (loader == null) loader = cl;
|
||||
return new DelegateApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateActivity(loader, className, intent);
|
||||
return create(className, DownloadActivity::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateReceiver(loader, className, intent);
|
||||
return create(className, DummyReceiver::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Service instantiateService(ClassLoader cl, String className, Intent intent)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (delegate != null)
|
||||
return delegate.instantiateService(loader, className, intent);
|
||||
return create(className, DummyService::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentProvider instantiateProvider(ClassLoader cl, String className)
|
||||
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
if (loader == null) loader = cl;
|
||||
if (delegate != null)
|
||||
return delegate.instantiateProvider(loader, className);
|
||||
return create(className, DummyProvider::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the class or dummy implementation if creation failed
|
||||
*/
|
||||
private <T> T create(String name, DummyFactory<T> factory) {
|
||||
try {
|
||||
return (T) loader.loadClass(name).newInstance();
|
||||
} catch (Exception ignored) {
|
||||
return factory.create();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,9 +4,11 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.net.Networking;
|
||||
import com.topjohnwu.magisk.net.Request;
|
||||
@@ -20,11 +22,13 @@ import java.io.File;
|
||||
import static android.R.string.no;
|
||||
import static android.R.string.ok;
|
||||
import static android.R.string.yes;
|
||||
import static com.topjohnwu.magisk.DelegateApplication.dynLoad;
|
||||
import static com.topjohnwu.magisk.R.string.dling;
|
||||
import static com.topjohnwu.magisk.R.string.no_internet_msg;
|
||||
import static com.topjohnwu.magisk.R.string.relaunch_app;
|
||||
import static com.topjohnwu.magisk.R.string.upgrade_msg;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
public class DownloadActivity extends Activity {
|
||||
|
||||
private static final String APP_NAME = "Magisk Manager";
|
||||
private static final String CDN_URL = "https://cdn.jsdelivr.net/gh/topjohnwu/magisk_files@%s/%s";
|
||||
@@ -107,11 +111,23 @@ public class MainActivity extends Activity {
|
||||
private void dlAPK() {
|
||||
dialog = ProgressDialog.show(themed, getString(dling), getString(dling) + " " + APP_NAME, true);
|
||||
// Download and upgrade the app
|
||||
File apk = new File(getCacheDir(), "manager.apk");
|
||||
request(apkLink).getAsFile(apk, file -> {
|
||||
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
|
||||
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
|
||||
if (dynLoad) {
|
||||
InjectAPK.setup(this);
|
||||
runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
APKInstall.install(this, file);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
78
stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java
Normal file
78
stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.AppComponentFactory;
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.utils.DynamicClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class InjectAPK {
|
||||
|
||||
static DelegateComponentFactory factory;
|
||||
|
||||
static Application setup(Context context) {
|
||||
File apk = DynAPK.current(context);
|
||||
File update = DynAPK.update(context);
|
||||
if (update.exists())
|
||||
update.renameTo(apk);
|
||||
Application delegate = null;
|
||||
if (!apk.exists()) {
|
||||
// Try copying APK
|
||||
Uri uri = new Uri.Builder().scheme("content")
|
||||
.authority("com.topjohnwu.magisk.provider")
|
||||
.encodedPath("apk_file").build();
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
try (InputStream is = resolver.openInputStream(uri)) {
|
||||
if (is != null) {
|
||||
try (OutputStream out = new FileOutputStream(apk)) {
|
||||
byte[] buf = new byte[4096];
|
||||
for (int read; (read = is.read(buf)) >= 0;) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||
}
|
||||
}
|
||||
if (apk.exists()) {
|
||||
ClassLoader cl = new DynamicClassLoader(apk, factory.loader);
|
||||
try {
|
||||
// Create the delegate AppComponentFactory
|
||||
AppComponentFactory df = (AppComponentFactory)
|
||||
cl.loadClass("androidx.core.app.CoreComponentFactory").newInstance();
|
||||
|
||||
// Create the delegate Application
|
||||
delegate = (Application) cl.loadClass("a.e").getConstructor(Object.class)
|
||||
.newInstance(DynAPK.pack(dynData()));
|
||||
|
||||
// If everything went well, set our loader and delegate
|
||||
factory.delegate = df;
|
||||
factory.loader = cl;
|
||||
} catch (Exception e) {
|
||||
Log.e(InjectAPK.class.getSimpleName(), "", e);
|
||||
apk.delete();
|
||||
}
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private static DynAPK.Data dynData() {
|
||||
DynAPK.Data data = new DynAPK.Data();
|
||||
data.version = BuildConfig.STUB_VERSION;
|
||||
// Public source code does not do component name obfuscation
|
||||
data.classToComponent = new HashMap<>();
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class DummyActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class DummyProvider extends ContentProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class DummyReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.topjohnwu.magisk.dummy;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class DummyService extends Service {
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
stopSelf();
|
||||
return null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user